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 internal { 29 30 namespace tray { 31 32 namespace { 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|. 37 void 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|. 52 void 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 67 class 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 99 class 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 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 bool bluetooth_discovering_; 403 404 DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView); 405 }; 406 407 } // namespace tray 408 409 TrayBluetooth::TrayBluetooth(SystemTray* system_tray) 410 : SystemTrayItem(system_tray), 411 default_(NULL), 412 detailed_(NULL) { 413 Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this); 414 } 415 416 TrayBluetooth::~TrayBluetooth() { 417 Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this); 418 } 419 420 views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) { 421 return NULL; 422 } 423 424 views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) { 425 CHECK(default_ == NULL); 426 default_ = new tray::BluetoothDefaultView( 427 this, status != user::LOGGED_IN_LOCKED); 428 return default_; 429 } 430 431 views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) { 432 if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable()) 433 return NULL; 434 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 435 ash::UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW); 436 CHECK(detailed_ == NULL); 437 detailed_ = new tray::BluetoothDetailedView(this, status); 438 return detailed_; 439 } 440 441 void TrayBluetooth::DestroyTrayView() { 442 } 443 444 void TrayBluetooth::DestroyDefaultView() { 445 default_ = NULL; 446 } 447 448 void TrayBluetooth::DestroyDetailedView() { 449 detailed_ = NULL; 450 } 451 452 void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) { 453 } 454 455 void TrayBluetooth::OnBluetoothRefresh() { 456 if (default_) 457 default_->UpdateLabel(); 458 else if (detailed_) 459 detailed_->Update(); 460 } 461 462 void TrayBluetooth::OnBluetoothDiscoveringChanged() { 463 if (!detailed_) 464 return; 465 detailed_->Update(); 466 } 467 468 } // namespace internal 469 } // namespace ash 470