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