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