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