Home | History | Annotate | Download | only in bluetooth
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ash/system/bluetooth/tray_bluetooth.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/fixed_sized_scroll_view.h"
      9 #include "ash/system/tray/hover_highlight_view.h"
     10 #include "ash/system/tray/system_tray.h"
     11 #include "ash/system/tray/system_tray_delegate.h"
     12 #include "ash/system/tray/system_tray_notifier.h"
     13 #include "ash/system/tray/throbber_view.h"
     14 #include "ash/system/tray/tray_constants.h"
     15 #include "ash/system/tray/tray_details_view.h"
     16 #include "ash/system/tray/tray_item_more.h"
     17 #include "ash/system/tray/tray_popup_header_button.h"
     18 #include "grit/ash_resources.h"
     19 #include "grit/ash_strings.h"
     20 #include "ui/base/l10n/l10n_util.h"
     21 #include "ui/base/resource/resource_bundle.h"
     22 #include "ui/gfx/image/image.h"
     23 #include "ui/views/controls/image_view.h"
     24 #include "ui/views/controls/label.h"
     25 #include "ui/views/layout/box_layout.h"
     26 
     27 namespace ash {
     28 namespace internal {
     29 
     30 namespace tray {
     31 
     32 namespace {
     33 
     34 // Updates bluetooth device |device| in the |list|. If it is new, append to the
     35 // end of the |list|; otherwise, keep it at the same place, but update the data
     36 // with new device info provided by |device|.
     37 void UpdateBluetoothDeviceList(BluetoothDeviceList* list,
     38                                const BluetoothDeviceInfo& device) {
     39   for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
     40        ++it) {
     41     if ((*it).address == device.address) {
     42       *it = device;
     43       return;
     44     }
     45   }
     46 
     47   list->push_back(device);
     48 }
     49 
     50 // Removes the obsolete BluetoothDevices from |list|, if they are not in the
     51 // |new_list|.
     52 void RemoveObsoleteBluetoothDevicesFromList(
     53     BluetoothDeviceList* list,
     54     const std::set<std::string>& new_list) {
     55   for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
     56        ++it) {
     57     if (new_list.find((*it).address) == new_list.end()) {
     58       it = list->erase(it);
     59       if (it == list->end())
     60         return;
     61     }
     62   }
     63 }
     64 
     65 }  // namespace
     66 
     67 class BluetoothDefaultView : public TrayItemMore {
     68  public:
     69   BluetoothDefaultView(SystemTrayItem* owner, bool show_more)
     70       : TrayItemMore(owner, show_more) {
     71     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
     72     SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_BLUETOOTH).ToImageSkia());
     73     UpdateLabel();
     74   }
     75 
     76   virtual ~BluetoothDefaultView() {}
     77 
     78   void UpdateLabel() {
     79     ash::SystemTrayDelegate* delegate =
     80         ash::Shell::GetInstance()->system_tray_delegate();
     81     if (delegate->GetBluetoothAvailable()) {
     82       ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     83       const base::string16 label =
     84           rb.GetLocalizedString(delegate->GetBluetoothEnabled() ?
     85               IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED :
     86               IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED);
     87       SetLabel(label);
     88       SetAccessibleName(label);
     89       SetVisible(true);
     90     } else {
     91       SetVisible(false);
     92     }
     93   }
     94 
     95  private:
     96   DISALLOW_COPY_AND_ASSIGN(BluetoothDefaultView);
     97 };
     98 
     99 class BluetoothDetailedView : public TrayDetailsView,
    100                               public ViewClickListener,
    101                               public views::ButtonListener {
    102  public:
    103   BluetoothDetailedView(SystemTrayItem* owner, user::LoginStatus login)
    104       : TrayDetailsView(owner),
    105         login_(login),
    106         manage_devices_(NULL),
    107         toggle_bluetooth_(NULL),
    108         enable_bluetooth_(NULL),
    109         bluetooth_discovering_(false) {
    110     CreateItems();
    111     Update();
    112   }
    113 
    114   virtual ~BluetoothDetailedView() {
    115     // Stop discovering bluetooth devices when exiting BT detailed view.
    116     BluetoothStopDiscovering();
    117   }
    118 
    119   void Update() {
    120     BluetoothStartDiscovering();
    121     UpdateBlueToothDeviceList();
    122 
    123     // Update UI.
    124     UpdateDeviceScrollList();
    125     UpdateHeaderEntry();
    126     Layout();
    127   }
    128 
    129  private:
    130   void CreateItems() {
    131     CreateScrollableList();
    132     AppendSettingsEntries();
    133     AppendHeaderEntry();
    134   }
    135 
    136   void BluetoothStartDiscovering() {
    137     ash::SystemTrayDelegate* delegate =
    138         ash::Shell::GetInstance()->system_tray_delegate();
    139     bool bluetooth_enabled = delegate->GetBluetoothEnabled();
    140     if (!bluetooth_discovering_ && bluetooth_enabled) {
    141       bluetooth_discovering_ = true;
    142       delegate->BluetoothStartDiscovering();
    143       throbber_->Start();
    144     } else if(!bluetooth_enabled) {
    145       bluetooth_discovering_ = false;
    146       throbber_->Stop();
    147     }
    148   }
    149 
    150   void BluetoothStopDiscovering() {
    151     ash::SystemTrayDelegate* delegate =
    152         ash::Shell::GetInstance()->system_tray_delegate();
    153     if (delegate && bluetooth_discovering_) {
    154       bluetooth_discovering_ = false;
    155       delegate->BluetoothStopDiscovering();
    156       throbber_->Stop();
    157     }
    158   }
    159 
    160   void UpdateBlueToothDeviceList() {
    161     std::set<std::string> new_connecting_devices;
    162     std::set<std::string> new_connected_devices;
    163     std::set<std::string> new_paired_not_connected_devices;
    164     std::set<std::string> new_discovered_not_paired_devices;
    165 
    166     BluetoothDeviceList list;
    167     Shell::GetInstance()->system_tray_delegate()->
    168         GetAvailableBluetoothDevices(&list);
    169     for (size_t i = 0; i < list.size(); ++i) {
    170       if (list[i].connecting) {
    171         list[i].display_name = l10n_util::GetStringFUTF16(
    172             IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, list[i].display_name);
    173         new_connecting_devices.insert(list[i].address);
    174         UpdateBluetoothDeviceList(&connecting_devices_, list[i]);
    175       } else if (list[i].connected && list[i].paired) {
    176         new_connected_devices.insert(list[i].address);
    177         UpdateBluetoothDeviceList(&connected_devices_, list[i]);
    178       } else if (list[i].paired) {
    179         new_paired_not_connected_devices.insert(list[i].address);
    180         UpdateBluetoothDeviceList(&paired_not_connected_devices_, list[i]);
    181       } else {
    182         new_discovered_not_paired_devices.insert(list[i].address);
    183         UpdateBluetoothDeviceList(&discovered_not_paired_devices_, list[i]);
    184       }
    185     }
    186     RemoveObsoleteBluetoothDevicesFromList(&connecting_devices_,
    187                                            new_connecting_devices);
    188     RemoveObsoleteBluetoothDevicesFromList(&connected_devices_,
    189                                            new_connected_devices);
    190     RemoveObsoleteBluetoothDevicesFromList(&paired_not_connected_devices_,
    191                                            new_paired_not_connected_devices);
    192     RemoveObsoleteBluetoothDevicesFromList(&discovered_not_paired_devices_,
    193                                            new_discovered_not_paired_devices);
    194   }
    195 
    196   void AppendHeaderEntry() {
    197     CreateSpecialRow(IDS_ASH_STATUS_TRAY_BLUETOOTH, this);
    198 
    199     if (login_ == user::LOGGED_IN_LOCKED)
    200       return;
    201 
    202     throbber_ = new ThrobberView;
    203     throbber_->SetTooltipText(
    204         l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
    205     footer()->AddThrobber(throbber_);
    206 
    207     // Do not allow toggling bluetooth in the lock screen.
    208     ash::SystemTrayDelegate* delegate =
    209         ash::Shell::GetInstance()->system_tray_delegate();
    210     toggle_bluetooth_ = new TrayPopupHeaderButton(this,
    211         IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED,
    212         IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED,
    213         IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED_HOVER,
    214         IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED_HOVER,
    215         IDS_ASH_STATUS_TRAY_BLUETOOTH);
    216     toggle_bluetooth_->SetToggled(!delegate->GetBluetoothEnabled());
    217     toggle_bluetooth_->SetTooltipText(
    218         l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_BLUETOOTH));
    219     toggle_bluetooth_->SetToggledTooltipText(
    220         l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH));
    221     footer()->AddButton(toggle_bluetooth_);
    222  }
    223 
    224   void UpdateHeaderEntry() {
    225     if (toggle_bluetooth_) {
    226       toggle_bluetooth_->SetToggled(
    227           !ash::Shell::GetInstance()->system_tray_delegate()->
    228               GetBluetoothEnabled());
    229     }
    230   }
    231 
    232   void UpdateDeviceScrollList() {
    233     device_map_.clear();
    234     scroll_content()->RemoveAllChildViews(true);
    235     enable_bluetooth_ = NULL;
    236 
    237     ash::SystemTrayDelegate* delegate =
    238         ash::Shell::GetInstance()->system_tray_delegate();
    239     bool bluetooth_enabled = delegate->GetBluetoothEnabled();
    240     bool blueooth_available = delegate->GetBluetoothAvailable();
    241     if (blueooth_available && !bluetooth_enabled &&
    242         toggle_bluetooth_) {
    243       enable_bluetooth_ =
    244           AddScrollListItem(
    245               l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH),
    246               gfx::Font::NORMAL, false, true);
    247     }
    248 
    249     AppendSameTypeDevicesToScrollList(
    250         connected_devices_, true, true, bluetooth_enabled);
    251     AppendSameTypeDevicesToScrollList(
    252         connecting_devices_, true, false, bluetooth_enabled);
    253     AppendSameTypeDevicesToScrollList(
    254         paired_not_connected_devices_, false, false, bluetooth_enabled);
    255     if (discovered_not_paired_devices_.size() > 0)
    256       AddScrollSeparator();
    257     AppendSameTypeDevicesToScrollList(
    258         discovered_not_paired_devices_, false, false, bluetooth_enabled);
    259 
    260     // Show user Bluetooth state if there is no bluetooth devices in list.
    261     if (device_map_.size() == 0) {
    262       if (blueooth_available && bluetooth_enabled) {
    263         AddScrollListItem(
    264             l10n_util::GetStringUTF16(
    265                 IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING),
    266             gfx::Font::NORMAL, false, true);
    267       }
    268     }
    269 
    270     scroll_content()->SizeToPreferredSize();
    271     static_cast<views::View*>(scroller())->Layout();
    272   }
    273 
    274   void AppendSameTypeDevicesToScrollList(const BluetoothDeviceList& list,
    275                                          bool bold,
    276                                          bool checked,
    277                                          bool enabled) {
    278     for (size_t i = 0; i < list.size(); ++i) {
    279       HoverHighlightView* container = AddScrollListItem(
    280           list[i].display_name,
    281           bold? gfx::Font::BOLD : gfx::Font::NORMAL,
    282           checked, enabled);
    283       device_map_[container] = list[i].address;
    284     }
    285   }
    286 
    287   HoverHighlightView* AddScrollListItem(const base::string16& text,
    288                                         gfx::Font::FontStyle style,
    289                                         bool checked,
    290                                         bool enabled) {
    291     HoverHighlightView* container = new HoverHighlightView(this);
    292     views::Label* label = container->AddCheckableLabel(text, style, checked);
    293     label->SetEnabled(enabled);
    294     scroll_content()->AddChildView(container);
    295     return container;
    296   }
    297 
    298   // Add settings entries.
    299   void AppendSettingsEntries() {
    300     // Add bluetooth device requires a browser window, hide it for non logged in
    301     // user.
    302     if (login_ == user::LOGGED_IN_NONE ||
    303         login_ == user::LOGGED_IN_LOCKED)
    304       return;
    305 
    306     ash::SystemTrayDelegate* delegate =
    307         ash::Shell::GetInstance()->system_tray_delegate();
    308     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    309     HoverHighlightView* container = new HoverHighlightView(this);
    310     container->AddLabel(
    311         rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BLUETOOTH_MANAGE_DEVICES),
    312         gfx::Font::NORMAL);
    313     container->SetEnabled(delegate->GetBluetoothAvailable());
    314     AddChildView(container);
    315     manage_devices_ = container;
    316   }
    317 
    318   // Returns true if the device with |device_id| is found in |device_list|,
    319   // and the display_name of the device will be returned in |display_name| if
    320   // it's not NULL.
    321   bool FoundDevice(const std::string& device_id,
    322                    const BluetoothDeviceList& device_list,
    323                    base::string16* display_name) {
    324     for (size_t i = 0; i < device_list.size(); ++i) {
    325       if (device_list[i].address == device_id) {
    326         if (display_name)
    327           *display_name = device_list[i].display_name;
    328         return true;
    329       }
    330     }
    331     return false;
    332   }
    333 
    334   // Updates UI of the clicked bluetooth device to show it is being connected
    335   // or disconnected if such an operation is going to be performed underway.
    336   void UpdateClickedDevice(std::string device_id, views::View* item_container) {
    337     base::string16 display_name;
    338     if (FoundDevice(device_id, paired_not_connected_devices_,
    339                            &display_name)) {
    340       display_name = l10n_util::GetStringFUTF16(
    341           IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, display_name);
    342 
    343       item_container->RemoveAllChildViews(true);
    344       static_cast<HoverHighlightView*>(item_container)->
    345           AddCheckableLabel(display_name, gfx::Font::BOLD, false);
    346       scroll_content()->SizeToPreferredSize();
    347       static_cast<views::View*>(scroller())->Layout();
    348     }
    349   }
    350 
    351   // Overridden from ViewClickListener.
    352   virtual void OnViewClicked(views::View* sender) OVERRIDE {
    353     ash::SystemTrayDelegate* delegate =
    354         ash::Shell::GetInstance()->system_tray_delegate();
    355     if (sender == footer()->content()) {
    356       owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
    357     } else if (sender == manage_devices_) {
    358       delegate->ManageBluetoothDevices();
    359     } else if (sender == enable_bluetooth_) {
    360       delegate->ToggleBluetooth();
    361     } else {
    362       if (!delegate->GetBluetoothEnabled())
    363         return;
    364       std::map<views::View*, std::string>::iterator find;
    365       find = device_map_.find(sender);
    366       if (find == device_map_.end())
    367         return;
    368       std::string device_id = find->second;
    369       if (FoundDevice(device_id, connecting_devices_, NULL))
    370         return;
    371       UpdateClickedDevice(device_id, sender);
    372       delegate->ConnectToBluetoothDevice(device_id);
    373     }
    374   }
    375 
    376   // Overridden from ButtonListener.
    377   virtual void ButtonPressed(views::Button* sender,
    378                              const ui::Event& event) OVERRIDE {
    379     ash::SystemTrayDelegate* delegate =
    380         ash::Shell::GetInstance()->system_tray_delegate();
    381     if (sender == toggle_bluetooth_)
    382       delegate->ToggleBluetooth();
    383     else
    384       NOTREACHED();
    385   }
    386 
    387   user::LoginStatus login_;
    388 
    389   std::map<views::View*, std::string> device_map_;
    390   views::View* manage_devices_;
    391   ThrobberView* throbber_;
    392   TrayPopupHeaderButton* toggle_bluetooth_;
    393   HoverHighlightView* enable_bluetooth_;
    394   BluetoothDeviceList connected_devices_;
    395   BluetoothDeviceList connecting_devices_;
    396   BluetoothDeviceList paired_not_connected_devices_;
    397   BluetoothDeviceList discovered_not_paired_devices_;
    398   bool bluetooth_discovering_;
    399 
    400   DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView);
    401 };
    402 
    403 }  // namespace tray
    404 
    405 TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
    406     : SystemTrayItem(system_tray),
    407       default_(NULL),
    408       detailed_(NULL) {
    409   Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
    410 }
    411 
    412 TrayBluetooth::~TrayBluetooth() {
    413   Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
    414 }
    415 
    416 views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
    417   return NULL;
    418 }
    419 
    420 views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) {
    421   CHECK(default_ == NULL);
    422   default_ = new tray::BluetoothDefaultView(
    423       this, status != user::LOGGED_IN_LOCKED);
    424   return default_;
    425 }
    426 
    427 views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) {
    428   if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable())
    429     return NULL;
    430   CHECK(detailed_ == NULL);
    431   detailed_ = new tray::BluetoothDetailedView(this, status);
    432   return detailed_;
    433 }
    434 
    435 void TrayBluetooth::DestroyTrayView() {
    436 }
    437 
    438 void TrayBluetooth::DestroyDefaultView() {
    439   default_ = NULL;
    440 }
    441 
    442 void TrayBluetooth::DestroyDetailedView() {
    443   detailed_ = NULL;
    444 }
    445 
    446 void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
    447 }
    448 
    449 void TrayBluetooth::OnBluetoothRefresh() {
    450   if (default_)
    451     default_->UpdateLabel();
    452   else if (detailed_)
    453     detailed_->Update();
    454 }
    455 
    456 void TrayBluetooth::OnBluetoothDiscoveringChanged() {
    457   if (!detailed_)
    458     return;
    459   detailed_->Update();
    460 }
    461 
    462 }  // namespace internal
    463 }  // namespace ash
    464