tray_bluetooth.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 tray { 29namespace { 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|. 34void 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|. 49void 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 64class 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 96class 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::Font::NORMAL); 314 container->SetEnabled(delegate->GetBluetoothAvailable()); 315 AddChildView(container); 316 manage_devices_ = container; 317 } 318 319 // Returns true if the device with |device_id| is found in |device_list|, 320 // and the display_name of the device will be returned in |display_name| if 321 // it's not NULL. 322 bool FoundDevice(const std::string& device_id, 323 const BluetoothDeviceList& device_list, 324 base::string16* display_name) { 325 for (size_t i = 0; i < device_list.size(); ++i) { 326 if (device_list[i].address == device_id) { 327 if (display_name) 328 *display_name = device_list[i].display_name; 329 return true; 330 } 331 } 332 return false; 333 } 334 335 // Updates UI of the clicked bluetooth device to show it is being connected 336 // or disconnected if such an operation is going to be performed underway. 337 void UpdateClickedDevice(std::string device_id, views::View* item_container) { 338 base::string16 display_name; 339 if (FoundDevice(device_id, paired_not_connected_devices_, 340 &display_name)) { 341 display_name = l10n_util::GetStringFUTF16( 342 IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, display_name); 343 344 item_container->RemoveAllChildViews(true); 345 static_cast<HoverHighlightView*>(item_container)-> 346 AddCheckableLabel(display_name, gfx::Font::BOLD, false); 347 scroll_content()->SizeToPreferredSize(); 348 static_cast<views::View*>(scroller())->Layout(); 349 } 350 } 351 352 // Overridden from ViewClickListener. 353 virtual void OnViewClicked(views::View* sender) OVERRIDE { 354 ash::SystemTrayDelegate* delegate = 355 ash::Shell::GetInstance()->system_tray_delegate(); 356 if (sender == footer()->content()) { 357 TransitionToDefaultView(); 358 } else if (sender == manage_devices_) { 359 delegate->ManageBluetoothDevices(); 360 } else if (sender == enable_bluetooth_) { 361 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 362 delegate->GetBluetoothEnabled() ? 363 ash::UMA_STATUS_AREA_BLUETOOTH_DISABLED : 364 ash::UMA_STATUS_AREA_BLUETOOTH_ENABLED); 365 delegate->ToggleBluetooth(); 366 } else { 367 if (!delegate->GetBluetoothEnabled()) 368 return; 369 std::map<views::View*, std::string>::iterator find; 370 find = device_map_.find(sender); 371 if (find == device_map_.end()) 372 return; 373 std::string device_id = find->second; 374 if (FoundDevice(device_id, connecting_devices_, NULL)) 375 return; 376 UpdateClickedDevice(device_id, sender); 377 delegate->ConnectToBluetoothDevice(device_id); 378 } 379 } 380 381 // Overridden from ButtonListener. 382 virtual void ButtonPressed(views::Button* sender, 383 const ui::Event& event) OVERRIDE { 384 ash::SystemTrayDelegate* delegate = 385 ash::Shell::GetInstance()->system_tray_delegate(); 386 if (sender == toggle_bluetooth_) 387 delegate->ToggleBluetooth(); 388 else 389 NOTREACHED(); 390 } 391 392 user::LoginStatus login_; 393 394 std::map<views::View*, std::string> device_map_; 395 views::View* manage_devices_; 396 ThrobberView* throbber_; 397 TrayPopupHeaderButton* toggle_bluetooth_; 398 HoverHighlightView* enable_bluetooth_; 399 BluetoothDeviceList connected_devices_; 400 BluetoothDeviceList connecting_devices_; 401 BluetoothDeviceList paired_not_connected_devices_; 402 BluetoothDeviceList discovered_not_paired_devices_; 403 404 DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView); 405}; 406 407} // namespace tray 408 409TrayBluetooth::TrayBluetooth(SystemTray* system_tray) 410 : SystemTrayItem(system_tray), 411 default_(NULL), 412 detailed_(NULL) { 413 Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this); 414} 415 416TrayBluetooth::~TrayBluetooth() { 417 Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this); 418} 419 420views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) { 421 return NULL; 422} 423 424views::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 431views::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 detailed_->Update(); 439 return detailed_; 440} 441 442void TrayBluetooth::DestroyTrayView() { 443} 444 445void TrayBluetooth::DestroyDefaultView() { 446 default_ = NULL; 447} 448 449void TrayBluetooth::DestroyDetailedView() { 450 detailed_ = NULL; 451} 452 453void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) { 454} 455 456void TrayBluetooth::OnBluetoothRefresh() { 457 if (default_) 458 default_->UpdateLabel(); 459 else if (detailed_) 460 detailed_->Update(); 461} 462 463void TrayBluetooth::OnBluetoothDiscoveringChanged() { 464 if (!detailed_) 465 return; 466 detailed_->Update(); 467} 468 469} // namespace ash 470