| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/bluetooth/tray_bluetooth.h" |
| |
| #include "ash/shell.h" |
| #include "ash/system/tray/fixed_sized_scroll_view.h" |
| #include "ash/system/tray/hover_highlight_view.h" |
| #include "ash/system/tray/system_tray.h" |
| #include "ash/system/tray/system_tray_delegate.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/tray/throbber_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_details_view.h" |
| #include "ash/system/tray/tray_item_more.h" |
| #include "ash/system/tray/tray_popup_header_button.h" |
| #include "grit/ash_resources.h" |
| #include "grit/ash_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/box_layout.h" |
| |
| namespace ash { |
| namespace internal { |
| |
| namespace tray { |
| |
| namespace { |
| |
| // Updates bluetooth device |device| in the |list|. If it is new, append to the |
| // end of the |list|; otherwise, keep it at the same place, but update the data |
| // with new device info provided by |device|. |
| void UpdateBluetoothDeviceList(BluetoothDeviceList* list, |
| const BluetoothDeviceInfo& device) { |
| for (BluetoothDeviceList::iterator it = list->begin(); it != list->end(); |
| ++it) { |
| if ((*it).address == device.address) { |
| *it = device; |
| return; |
| } |
| } |
| |
| list->push_back(device); |
| } |
| |
| // Removes the obsolete BluetoothDevices from |list|, if they are not in the |
| // |new_list|. |
| void RemoveObsoleteBluetoothDevicesFromList( |
| BluetoothDeviceList* list, |
| const std::set<std::string>& new_list) { |
| for (BluetoothDeviceList::iterator it = list->begin(); it != list->end(); |
| ++it) { |
| if (new_list.find((*it).address) == new_list.end()) { |
| it = list->erase(it); |
| if (it == list->end()) |
| return; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| class BluetoothDefaultView : public TrayItemMore { |
| public: |
| BluetoothDefaultView(SystemTrayItem* owner, bool show_more) |
| : TrayItemMore(owner, show_more) { |
| ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_BLUETOOTH).ToImageSkia()); |
| UpdateLabel(); |
| } |
| |
| virtual ~BluetoothDefaultView() {} |
| |
| void UpdateLabel() { |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| if (delegate->GetBluetoothAvailable()) { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| const base::string16 label = |
| rb.GetLocalizedString(delegate->GetBluetoothEnabled() ? |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED : |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED); |
| SetLabel(label); |
| SetAccessibleName(label); |
| SetVisible(true); |
| } else { |
| SetVisible(false); |
| } |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BluetoothDefaultView); |
| }; |
| |
| class BluetoothDetailedView : public TrayDetailsView, |
| public ViewClickListener, |
| public views::ButtonListener { |
| public: |
| BluetoothDetailedView(SystemTrayItem* owner, user::LoginStatus login) |
| : TrayDetailsView(owner), |
| login_(login), |
| manage_devices_(NULL), |
| toggle_bluetooth_(NULL), |
| enable_bluetooth_(NULL), |
| bluetooth_discovering_(false) { |
| CreateItems(); |
| Update(); |
| } |
| |
| virtual ~BluetoothDetailedView() { |
| // Stop discovering bluetooth devices when exiting BT detailed view. |
| BluetoothStopDiscovering(); |
| } |
| |
| void Update() { |
| BluetoothStartDiscovering(); |
| UpdateBlueToothDeviceList(); |
| |
| // Update UI. |
| UpdateDeviceScrollList(); |
| UpdateHeaderEntry(); |
| Layout(); |
| } |
| |
| private: |
| void CreateItems() { |
| CreateScrollableList(); |
| AppendSettingsEntries(); |
| AppendHeaderEntry(); |
| } |
| |
| void BluetoothStartDiscovering() { |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| bool bluetooth_enabled = delegate->GetBluetoothEnabled(); |
| if (!bluetooth_discovering_ && bluetooth_enabled) { |
| bluetooth_discovering_ = true; |
| delegate->BluetoothStartDiscovering(); |
| throbber_->Start(); |
| } else if(!bluetooth_enabled) { |
| bluetooth_discovering_ = false; |
| throbber_->Stop(); |
| } |
| } |
| |
| void BluetoothStopDiscovering() { |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| if (delegate && bluetooth_discovering_) { |
| bluetooth_discovering_ = false; |
| delegate->BluetoothStopDiscovering(); |
| throbber_->Stop(); |
| } |
| } |
| |
| void UpdateBlueToothDeviceList() { |
| std::set<std::string> new_connecting_devices; |
| std::set<std::string> new_connected_devices; |
| std::set<std::string> new_paired_not_connected_devices; |
| std::set<std::string> new_discovered_not_paired_devices; |
| |
| BluetoothDeviceList list; |
| Shell::GetInstance()->system_tray_delegate()-> |
| GetAvailableBluetoothDevices(&list); |
| for (size_t i = 0; i < list.size(); ++i) { |
| if (list[i].connecting) { |
| list[i].display_name = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, list[i].display_name); |
| new_connecting_devices.insert(list[i].address); |
| UpdateBluetoothDeviceList(&connecting_devices_, list[i]); |
| } else if (list[i].connected && list[i].paired) { |
| new_connected_devices.insert(list[i].address); |
| UpdateBluetoothDeviceList(&connected_devices_, list[i]); |
| } else if (list[i].paired) { |
| new_paired_not_connected_devices.insert(list[i].address); |
| UpdateBluetoothDeviceList(&paired_not_connected_devices_, list[i]); |
| } else { |
| new_discovered_not_paired_devices.insert(list[i].address); |
| UpdateBluetoothDeviceList(&discovered_not_paired_devices_, list[i]); |
| } |
| } |
| RemoveObsoleteBluetoothDevicesFromList(&connecting_devices_, |
| new_connecting_devices); |
| RemoveObsoleteBluetoothDevicesFromList(&connected_devices_, |
| new_connected_devices); |
| RemoveObsoleteBluetoothDevicesFromList(&paired_not_connected_devices_, |
| new_paired_not_connected_devices); |
| RemoveObsoleteBluetoothDevicesFromList(&discovered_not_paired_devices_, |
| new_discovered_not_paired_devices); |
| } |
| |
| void AppendHeaderEntry() { |
| CreateSpecialRow(IDS_ASH_STATUS_TRAY_BLUETOOTH, this); |
| |
| if (login_ == user::LOGGED_IN_LOCKED) |
| return; |
| |
| throbber_ = new ThrobberView; |
| throbber_->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING)); |
| footer()->AddThrobber(throbber_); |
| |
| // Do not allow toggling bluetooth in the lock screen. |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| toggle_bluetooth_ = new TrayPopupHeaderButton(this, |
| IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED, |
| IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED, |
| IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED_HOVER, |
| IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED_HOVER, |
| IDS_ASH_STATUS_TRAY_BLUETOOTH); |
| toggle_bluetooth_->SetToggled(!delegate->GetBluetoothEnabled()); |
| toggle_bluetooth_->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_BLUETOOTH)); |
| toggle_bluetooth_->SetToggledTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH)); |
| footer()->AddButton(toggle_bluetooth_); |
| } |
| |
| void UpdateHeaderEntry() { |
| if (toggle_bluetooth_) { |
| toggle_bluetooth_->SetToggled( |
| !ash::Shell::GetInstance()->system_tray_delegate()-> |
| GetBluetoothEnabled()); |
| } |
| } |
| |
| void UpdateDeviceScrollList() { |
| device_map_.clear(); |
| scroll_content()->RemoveAllChildViews(true); |
| enable_bluetooth_ = NULL; |
| |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| bool bluetooth_enabled = delegate->GetBluetoothEnabled(); |
| bool blueooth_available = delegate->GetBluetoothAvailable(); |
| if (blueooth_available && !bluetooth_enabled && |
| toggle_bluetooth_) { |
| enable_bluetooth_ = |
| AddScrollListItem( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH), |
| gfx::Font::NORMAL, false, true); |
| } |
| |
| AppendSameTypeDevicesToScrollList( |
| connected_devices_, true, true, bluetooth_enabled); |
| AppendSameTypeDevicesToScrollList( |
| connecting_devices_, true, false, bluetooth_enabled); |
| AppendSameTypeDevicesToScrollList( |
| paired_not_connected_devices_, false, false, bluetooth_enabled); |
| if (discovered_not_paired_devices_.size() > 0) |
| AddScrollSeparator(); |
| AppendSameTypeDevicesToScrollList( |
| discovered_not_paired_devices_, false, false, bluetooth_enabled); |
| |
| // Show user Bluetooth state if there is no bluetooth devices in list. |
| if (device_map_.size() == 0) { |
| if (blueooth_available && bluetooth_enabled) { |
| AddScrollListItem( |
| l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING), |
| gfx::Font::NORMAL, false, true); |
| } |
| } |
| |
| scroll_content()->SizeToPreferredSize(); |
| static_cast<views::View*>(scroller())->Layout(); |
| } |
| |
| void AppendSameTypeDevicesToScrollList(const BluetoothDeviceList& list, |
| bool bold, |
| bool checked, |
| bool enabled) { |
| for (size_t i = 0; i < list.size(); ++i) { |
| HoverHighlightView* container = AddScrollListItem( |
| list[i].display_name, |
| bold? gfx::Font::BOLD : gfx::Font::NORMAL, |
| checked, enabled); |
| device_map_[container] = list[i].address; |
| } |
| } |
| |
| HoverHighlightView* AddScrollListItem(const base::string16& text, |
| gfx::Font::FontStyle style, |
| bool checked, |
| bool enabled) { |
| HoverHighlightView* container = new HoverHighlightView(this); |
| views::Label* label = container->AddCheckableLabel(text, style, checked); |
| label->SetEnabled(enabled); |
| scroll_content()->AddChildView(container); |
| return container; |
| } |
| |
| // Add settings entries. |
| void AppendSettingsEntries() { |
| // Add bluetooth device requires a browser window, hide it for non logged in |
| // user. |
| if (login_ == user::LOGGED_IN_NONE || |
| login_ == user::LOGGED_IN_LOCKED) |
| return; |
| |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| HoverHighlightView* container = new HoverHighlightView(this); |
| container->AddLabel( |
| rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BLUETOOTH_MANAGE_DEVICES), |
| gfx::Font::NORMAL); |
| container->SetEnabled(delegate->GetBluetoothAvailable()); |
| AddChildView(container); |
| manage_devices_ = container; |
| } |
| |
| // Returns true if the device with |device_id| is found in |device_list|, |
| // and the display_name of the device will be returned in |display_name| if |
| // it's not NULL. |
| bool FoundDevice(const std::string& device_id, |
| const BluetoothDeviceList& device_list, |
| base::string16* display_name) { |
| for (size_t i = 0; i < device_list.size(); ++i) { |
| if (device_list[i].address == device_id) { |
| if (display_name) |
| *display_name = device_list[i].display_name; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Updates UI of the clicked bluetooth device to show it is being connected |
| // or disconnected if such an operation is going to be performed underway. |
| void UpdateClickedDevice(std::string device_id, views::View* item_container) { |
| base::string16 display_name; |
| if (FoundDevice(device_id, paired_not_connected_devices_, |
| &display_name)) { |
| display_name = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, display_name); |
| |
| item_container->RemoveAllChildViews(true); |
| static_cast<HoverHighlightView*>(item_container)-> |
| AddCheckableLabel(display_name, gfx::Font::BOLD, false); |
| scroll_content()->SizeToPreferredSize(); |
| static_cast<views::View*>(scroller())->Layout(); |
| } |
| } |
| |
| // Overridden from ViewClickListener. |
| virtual void OnViewClicked(views::View* sender) OVERRIDE { |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| if (sender == footer()->content()) { |
| TransitionToDefaultView(); |
| } else if (sender == manage_devices_) { |
| delegate->ManageBluetoothDevices(); |
| } else if (sender == enable_bluetooth_) { |
| Shell::GetInstance()->metrics()->RecordUserMetricsAction( |
| delegate->GetBluetoothEnabled() ? |
| ash::UMA_STATUS_AREA_BLUETOOTH_DISABLED : |
| ash::UMA_STATUS_AREA_BLUETOOTH_ENABLED); |
| delegate->ToggleBluetooth(); |
| } else { |
| if (!delegate->GetBluetoothEnabled()) |
| return; |
| std::map<views::View*, std::string>::iterator find; |
| find = device_map_.find(sender); |
| if (find == device_map_.end()) |
| return; |
| std::string device_id = find->second; |
| if (FoundDevice(device_id, connecting_devices_, NULL)) |
| return; |
| UpdateClickedDevice(device_id, sender); |
| delegate->ConnectToBluetoothDevice(device_id); |
| } |
| } |
| |
| // Overridden from ButtonListener. |
| virtual void ButtonPressed(views::Button* sender, |
| const ui::Event& event) OVERRIDE { |
| ash::SystemTrayDelegate* delegate = |
| ash::Shell::GetInstance()->system_tray_delegate(); |
| if (sender == toggle_bluetooth_) |
| delegate->ToggleBluetooth(); |
| else |
| NOTREACHED(); |
| } |
| |
| user::LoginStatus login_; |
| |
| std::map<views::View*, std::string> device_map_; |
| views::View* manage_devices_; |
| ThrobberView* throbber_; |
| TrayPopupHeaderButton* toggle_bluetooth_; |
| HoverHighlightView* enable_bluetooth_; |
| BluetoothDeviceList connected_devices_; |
| BluetoothDeviceList connecting_devices_; |
| BluetoothDeviceList paired_not_connected_devices_; |
| BluetoothDeviceList discovered_not_paired_devices_; |
| bool bluetooth_discovering_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView); |
| }; |
| |
| } // namespace tray |
| |
| TrayBluetooth::TrayBluetooth(SystemTray* system_tray) |
| : SystemTrayItem(system_tray), |
| default_(NULL), |
| detailed_(NULL) { |
| Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this); |
| } |
| |
| TrayBluetooth::~TrayBluetooth() { |
| Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this); |
| } |
| |
| views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) { |
| return NULL; |
| } |
| |
| views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) { |
| CHECK(default_ == NULL); |
| default_ = new tray::BluetoothDefaultView( |
| this, status != user::LOGGED_IN_LOCKED); |
| return default_; |
| } |
| |
| views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) { |
| if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable()) |
| return NULL; |
| Shell::GetInstance()->metrics()->RecordUserMetricsAction( |
| ash::UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW); |
| CHECK(detailed_ == NULL); |
| detailed_ = new tray::BluetoothDetailedView(this, status); |
| return detailed_; |
| } |
| |
| void TrayBluetooth::DestroyTrayView() { |
| } |
| |
| void TrayBluetooth::DestroyDefaultView() { |
| default_ = NULL; |
| } |
| |
| void TrayBluetooth::DestroyDetailedView() { |
| detailed_ = NULL; |
| } |
| |
| void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) { |
| } |
| |
| void TrayBluetooth::OnBluetoothRefresh() { |
| if (default_) |
| default_->UpdateLabel(); |
| else if (detailed_) |
| detailed_->Update(); |
| } |
| |
| void TrayBluetooth::OnBluetoothDiscoveringChanged() { |
| if (!detailed_) |
| return; |
| detailed_->Update(); |
| } |
| |
| } // namespace internal |
| } // namespace ash |