• 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/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|.
UpdateBluetoothDeviceList(BluetoothDeviceList * list,const BluetoothDeviceInfo & 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|.
RemoveObsoleteBluetoothDevicesFromList(BluetoothDeviceList * list,const std::set<std::string> & 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:
BluetoothDefaultView(SystemTrayItem * owner,bool show_more)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 
~BluetoothDefaultView()76   virtual ~BluetoothDefaultView() {}
77 
UpdateLabel()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:
BluetoothDetailedView(SystemTrayItem * owner,user::LoginStatus login)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 
~BluetoothDetailedView()114   virtual ~BluetoothDetailedView() {
115     // Stop discovering bluetooth devices when exiting BT detailed view.
116     BluetoothStopDiscovering();
117   }
118 
Update()119   void Update() {
120     BluetoothStartDiscovering();
121     UpdateBlueToothDeviceList();
122 
123     // Update UI.
124     UpdateDeviceScrollList();
125     UpdateHeaderEntry();
126     Layout();
127   }
128 
129  private:
CreateItems()130   void CreateItems() {
131     CreateScrollableList();
132     AppendSettingsEntries();
133     AppendHeaderEntry();
134   }
135 
BluetoothStartDiscovering()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 
BluetoothStopDiscovering()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 
UpdateBlueToothDeviceList()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 
AppendHeaderEntry()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 
UpdateHeaderEntry()224   void UpdateHeaderEntry() {
225     if (toggle_bluetooth_) {
226       toggle_bluetooth_->SetToggled(
227           !ash::Shell::GetInstance()->system_tray_delegate()->
228               GetBluetoothEnabled());
229     }
230   }
231 
UpdateDeviceScrollList()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 
AppendSameTypeDevicesToScrollList(const BluetoothDeviceList & list,bool bold,bool checked,bool enabled)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 
AddScrollListItem(const base::string16 & text,gfx::Font::FontStyle style,bool checked,bool enabled)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.
AppendSettingsEntries()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.
FoundDevice(const std::string & device_id,const BluetoothDeviceList & device_list,base::string16 * display_name)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.
UpdateClickedDevice(std::string device_id,views::View * item_container)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.
OnViewClicked(views::View * sender)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       TransitionToDefaultView();
357     } else if (sender == manage_devices_) {
358       delegate->ManageBluetoothDevices();
359     } else if (sender == enable_bluetooth_) {
360       Shell::GetInstance()->metrics()->RecordUserMetricsAction(
361           delegate->GetBluetoothEnabled() ?
362           ash::UMA_STATUS_AREA_BLUETOOTH_DISABLED :
363           ash::UMA_STATUS_AREA_BLUETOOTH_ENABLED);
364       delegate->ToggleBluetooth();
365     } else {
366       if (!delegate->GetBluetoothEnabled())
367         return;
368       std::map<views::View*, std::string>::iterator find;
369       find = device_map_.find(sender);
370       if (find == device_map_.end())
371         return;
372       std::string device_id = find->second;
373       if (FoundDevice(device_id, connecting_devices_, NULL))
374         return;
375       UpdateClickedDevice(device_id, sender);
376       delegate->ConnectToBluetoothDevice(device_id);
377     }
378   }
379 
380   // Overridden from ButtonListener.
ButtonPressed(views::Button * sender,const ui::Event & event)381   virtual void ButtonPressed(views::Button* sender,
382                              const ui::Event& event) OVERRIDE {
383     ash::SystemTrayDelegate* delegate =
384         ash::Shell::GetInstance()->system_tray_delegate();
385     if (sender == toggle_bluetooth_)
386       delegate->ToggleBluetooth();
387     else
388       NOTREACHED();
389   }
390 
391   user::LoginStatus login_;
392 
393   std::map<views::View*, std::string> device_map_;
394   views::View* manage_devices_;
395   ThrobberView* throbber_;
396   TrayPopupHeaderButton* toggle_bluetooth_;
397   HoverHighlightView* enable_bluetooth_;
398   BluetoothDeviceList connected_devices_;
399   BluetoothDeviceList connecting_devices_;
400   BluetoothDeviceList paired_not_connected_devices_;
401   BluetoothDeviceList discovered_not_paired_devices_;
402   bool bluetooth_discovering_;
403 
404   DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView);
405 };
406 
407 }  // namespace tray
408 
TrayBluetooth(SystemTray * system_tray)409 TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
410     : SystemTrayItem(system_tray),
411       default_(NULL),
412       detailed_(NULL) {
413   Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
414 }
415 
~TrayBluetooth()416 TrayBluetooth::~TrayBluetooth() {
417   Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
418 }
419 
CreateTrayView(user::LoginStatus status)420 views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
421   return NULL;
422 }
423 
CreateDefaultView(user::LoginStatus status)424 views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) {
425   CHECK(default_ == NULL);
426   default_ = new tray::BluetoothDefaultView(
427       this, status != user::LOGGED_IN_LOCKED);
428   return default_;
429 }
430 
CreateDetailedView(user::LoginStatus status)431 views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) {
432   if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable())
433     return NULL;
434   Shell::GetInstance()->metrics()->RecordUserMetricsAction(
435       ash::UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW);
436   CHECK(detailed_ == NULL);
437   detailed_ = new tray::BluetoothDetailedView(this, status);
438   return detailed_;
439 }
440 
DestroyTrayView()441 void TrayBluetooth::DestroyTrayView() {
442 }
443 
DestroyDefaultView()444 void TrayBluetooth::DestroyDefaultView() {
445   default_ = NULL;
446 }
447 
DestroyDetailedView()448 void TrayBluetooth::DestroyDetailedView() {
449   detailed_ = NULL;
450 }
451 
UpdateAfterLoginStatusChange(user::LoginStatus status)452 void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
453 }
454 
OnBluetoothRefresh()455 void TrayBluetooth::OnBluetoothRefresh() {
456   if (default_)
457     default_->UpdateLabel();
458   else if (detailed_)
459     detailed_->Update();
460 }
461 
OnBluetoothDiscoveringChanged()462 void TrayBluetooth::OnBluetoothDiscoveringChanged() {
463   if (!detailed_)
464     return;
465   detailed_->Update();
466 }
467 
468 }  // namespace internal
469 }  // namespace ash
470