tray_bluetooth.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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
27namespace ash {
28namespace tray {
29namespace {
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|.
34void 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|.
49void 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
64class 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
96class 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
410TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
411    : SystemTrayItem(system_tray),
412      default_(NULL),
413      detailed_(NULL) {
414  Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
415}
416
417TrayBluetooth::~TrayBluetooth() {
418  Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
419}
420
421views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
422  return NULL;
423}
424
425views::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
432views::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
443void TrayBluetooth::DestroyTrayView() {
444}
445
446void TrayBluetooth::DestroyDefaultView() {
447  default_ = NULL;
448}
449
450void TrayBluetooth::DestroyDetailedView() {
451  detailed_ = NULL;
452}
453
454void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
455}
456
457void TrayBluetooth::OnBluetoothRefresh() {
458  if (default_)
459    default_->UpdateLabel();
460  else if (detailed_)
461    detailed_->Update();
462}
463
464void TrayBluetooth::OnBluetoothDiscoveringChanged() {
465  if (!detailed_)
466    return;
467  detailed_->Update();
468}
469
470}  // namespace ash
471