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