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