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