tray_bluetooth.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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 internal {
29
30namespace tray {
31
32namespace {
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|.
37void UpdateBluetoothDeviceListHelper(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|.
52void 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
67class BluetoothDefaultView : public TrayItemMore {
68 public:
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
76  virtual ~BluetoothDefaultView() {}
77
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
99class BluetoothDetailedView : public TrayDetailsView,
100                              public ViewClickListener,
101                              public views::ButtonListener {
102 public:
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    CreateItems();
110  }
111
112  virtual ~BluetoothDetailedView() {
113    // Stop discovering bluetooth devices when exiting BT detailed view.
114    BluetoothStopDiscovering();
115  }
116
117  void Update() {
118    BluetoothStartDiscovering();
119    UpdateBluetoothDeviceList();
120
121    // Update UI.
122    UpdateDeviceScrollList();
123    UpdateHeaderEntry();
124    Layout();
125  }
126
127 private:
128  void CreateItems() {
129    CreateScrollableList();
130    AppendSettingsEntries();
131    AppendHeaderEntry();
132  }
133
134  void BluetoothStartDiscovering() {
135    ash::SystemTrayDelegate* delegate =
136        ash::Shell::GetInstance()->system_tray_delegate();
137    bool bluetooth_enabled = delegate->GetBluetoothEnabled();
138    bool bluetooth_discovering = delegate->GetBluetoothDiscovering();
139    if (bluetooth_discovering) {
140      throbber_->Start();
141      return;
142    }
143    throbber_->Stop();
144    if (bluetooth_enabled) {
145      delegate->BluetoothStartDiscovering();
146    }
147  }
148
149  void BluetoothStopDiscovering() {
150    ash::SystemTrayDelegate* delegate =
151        ash::Shell::GetInstance()->system_tray_delegate();
152    if (delegate && delegate->GetBluetoothDiscovering()) {
153      delegate->BluetoothStopDiscovering();
154      throbber_->Stop();
155    }
156  }
157
158  void UpdateBluetoothDeviceList() {
159    std::set<std::string> new_connecting_devices;
160    std::set<std::string> new_connected_devices;
161    std::set<std::string> new_paired_not_connected_devices;
162    std::set<std::string> new_discovered_not_paired_devices;
163
164    BluetoothDeviceList list;
165    Shell::GetInstance()->system_tray_delegate()->
166        GetAvailableBluetoothDevices(&list);
167    for (size_t i = 0; i < list.size(); ++i) {
168      if (list[i].connecting) {
169        list[i].display_name = l10n_util::GetStringFUTF16(
170            IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, list[i].display_name);
171        new_connecting_devices.insert(list[i].address);
172        UpdateBluetoothDeviceListHelper(&connecting_devices_, list[i]);
173      } else if (list[i].connected && list[i].paired) {
174        new_connected_devices.insert(list[i].address);
175        UpdateBluetoothDeviceListHelper(&connected_devices_, list[i]);
176      } else if (list[i].paired) {
177        new_paired_not_connected_devices.insert(list[i].address);
178        UpdateBluetoothDeviceListHelper(
179            &paired_not_connected_devices_, list[i]);
180      } else {
181        new_discovered_not_paired_devices.insert(list[i].address);
182        UpdateBluetoothDeviceListHelper(
183            &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
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
224  void UpdateHeaderEntry() {
225    if (toggle_bluetooth_) {
226      toggle_bluetooth_->SetToggled(
227          !ash::Shell::GetInstance()->system_tray_delegate()->
228              GetBluetoothEnabled());
229    }
230  }
231
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
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
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.
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.
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.
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.
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.
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
403  DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView);
404};
405
406}  // namespace tray
407
408TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
409    : SystemTrayItem(system_tray),
410      default_(NULL),
411      detailed_(NULL) {
412  Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
413}
414
415TrayBluetooth::~TrayBluetooth() {
416  Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
417}
418
419views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
420  return NULL;
421}
422
423views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) {
424  CHECK(default_ == NULL);
425  default_ = new tray::BluetoothDefaultView(
426      this, status != user::LOGGED_IN_LOCKED);
427  return default_;
428}
429
430views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) {
431  if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable())
432    return NULL;
433  Shell::GetInstance()->metrics()->RecordUserMetricsAction(
434      ash::UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW);
435  CHECK(detailed_ == NULL);
436  detailed_ = new tray::BluetoothDetailedView(this, status);
437  detailed_->Update();
438  return detailed_;
439}
440
441void TrayBluetooth::DestroyTrayView() {
442}
443
444void TrayBluetooth::DestroyDefaultView() {
445  default_ = NULL;
446}
447
448void TrayBluetooth::DestroyDetailedView() {
449  detailed_ = NULL;
450}
451
452void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
453}
454
455void TrayBluetooth::OnBluetoothRefresh() {
456  if (default_)
457    default_->UpdateLabel();
458  else if (detailed_)
459    detailed_->Update();
460}
461
462void TrayBluetooth::OnBluetoothDiscoveringChanged() {
463  if (!detailed_)
464    return;
465  detailed_->Update();
466}
467
468}  // namespace internal
469}  // namespace ash
470