• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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|.
UpdateBluetoothDeviceListHelper(BluetoothDeviceList * list,const BluetoothDeviceInfo & 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|.
RemoveObsoleteBluetoothDevicesFromList(BluetoothDeviceList * list,const std::set<std::string> & 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:
BluetoothDefaultView(SystemTrayItem * owner,bool show_more)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 
~BluetoothDefaultView()74   virtual ~BluetoothDefaultView() {}
75 
UpdateLabel()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:
BluetoothDetailedView(SystemTrayItem * owner,user::LoginStatus login)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 
~BluetoothDetailedView()110   virtual ~BluetoothDetailedView() {
111     // Stop discovering bluetooth devices when exiting BT detailed view.
112     BluetoothStopDiscovering();
113   }
114 
Update()115   void Update() {
116     BluetoothStartDiscovering();
117     UpdateBluetoothDeviceList();
118 
119     // Update UI.
120     UpdateDeviceScrollList();
121     UpdateHeaderEntry();
122     Layout();
123   }
124 
125  private:
CreateItems()126   void CreateItems() {
127     CreateScrollableList();
128     AppendSettingsEntries();
129     AppendHeaderEntry();
130   }
131 
BluetoothStartDiscovering()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 
BluetoothStopDiscovering()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 
UpdateBluetoothDeviceList()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 
AppendHeaderEntry()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 
UpdateHeaderEntry()222   void UpdateHeaderEntry() {
223     if (toggle_bluetooth_) {
224       toggle_bluetooth_->SetToggled(
225           !ash::Shell::GetInstance()->system_tray_delegate()->
226               GetBluetoothEnabled());
227     }
228   }
229 
UpdateDeviceScrollList()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 
AppendSameTypeDevicesToScrollList(const BluetoothDeviceList & list,bool bold,bool checked,bool enabled)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 
AddScrollListItem(const base::string16 & text,gfx::Font::FontStyle style,bool checked,bool enabled)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.
AppendSettingsEntries()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.
FoundDevice(const std::string & device_id,const BluetoothDeviceList & device_list,base::string16 * display_name)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.
UpdateClickedDevice(std::string device_id,views::View * item_container)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.
OnViewClicked(views::View * sender)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.
ButtonPressed(views::Button * sender,const ui::Event & event)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 
TrayBluetooth(SystemTray * system_tray)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 
~TrayBluetooth()423 TrayBluetooth::~TrayBluetooth() {
424   Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
425 }
426 
CreateTrayView(user::LoginStatus status)427 views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
428   return NULL;
429 }
430 
CreateDefaultView(user::LoginStatus status)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 
CreateDetailedView(user::LoginStatus status)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 
DestroyTrayView()449 void TrayBluetooth::DestroyTrayView() {
450 }
451 
DestroyDefaultView()452 void TrayBluetooth::DestroyDefaultView() {
453   default_ = NULL;
454 }
455 
DestroyDetailedView()456 void TrayBluetooth::DestroyDetailedView() {
457   detailed_ = NULL;
458 }
459 
UpdateAfterLoginStatusChange(user::LoginStatus status)460 void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
461 }
462 
OnBluetoothRefresh()463 void TrayBluetooth::OnBluetoothRefresh() {
464   if (default_)
465     default_->UpdateLabel();
466   else if (detailed_)
467     detailed_->Update();
468 }
469 
OnBluetoothDiscoveringChanged()470 void TrayBluetooth::OnBluetoothDiscoveringChanged() {
471   if (!detailed_)
472     return;
473   detailed_->Update();
474 }
475 
476 }  // namespace ash
477