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|. 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|. 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: 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 74 virtual ~BluetoothDefaultView() {} 75 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: 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 110 virtual ~BluetoothDetailedView() { 111 // Stop discovering bluetooth devices when exiting BT detailed view. 112 BluetoothStopDiscovering(); 113 } 114 115 void Update() { 116 BluetoothStartDiscovering(); 117 UpdateBluetoothDeviceList(); 118 119 // Update UI. 120 UpdateDeviceScrollList(); 121 UpdateHeaderEntry(); 122 Layout(); 123 } 124 125 private: 126 void CreateItems() { 127 CreateScrollableList(); 128 AppendSettingsEntries(); 129 AppendHeaderEntry(); 130 } 131 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 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 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 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 222 void UpdateHeaderEntry() { 223 if (toggle_bluetooth_) { 224 toggle_bluetooth_->SetToggled( 225 !ash::Shell::GetInstance()->system_tray_delegate()-> 226 GetBluetoothEnabled()); 227 } 228 } 229 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 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 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. 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. 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. 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. 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. 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 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 423 TrayBluetooth::~TrayBluetooth() { 424 Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this); 425 } 426 427 views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) { 428 return NULL; 429 } 430 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 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 449 void TrayBluetooth::DestroyTrayView() { 450 } 451 452 void TrayBluetooth::DestroyDefaultView() { 453 default_ = NULL; 454 } 455 456 void TrayBluetooth::DestroyDetailedView() { 457 detailed_ = NULL; 458 } 459 460 void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) { 461 } 462 463 void TrayBluetooth::OnBluetoothRefresh() { 464 if (default_) 465 default_->UpdateLabel(); 466 else if (detailed_) 467 detailed_->Update(); 468 } 469 470 void TrayBluetooth::OnBluetoothDiscoveringChanged() { 471 if (!detailed_) 472 return; 473 detailed_->Update(); 474 } 475 476 } // namespace ash 477