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