// 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 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 UpdateBluetoothDeviceListHelper(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) {
CreateItems();
}
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();
bool bluetooth_discovering = delegate->GetBluetoothDiscovering();
if (bluetooth_discovering) {
throbber_->Start();
return;
}
throbber_->Stop();
if (bluetooth_enabled) {
delegate->BluetoothStartDiscovering();
}
}
void BluetoothStopDiscovering() {
ash::SystemTrayDelegate* delegate =
ash::Shell::GetInstance()->system_tray_delegate();
if (delegate && delegate->GetBluetoothDiscovering()) {
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);
UpdateBluetoothDeviceListHelper(&connecting_devices_, list[i]);
} else if (list[i].connected && list[i].paired) {
new_connected_devices.insert(list[i].address);
UpdateBluetoothDeviceListHelper(&connected_devices_, list[i]);
} else if (list[i].paired) {
new_paired_not_connected_devices.insert(list[i].address);
UpdateBluetoothDeviceListHelper(
&paired_not_connected_devices_, list[i]);
} else {
new_discovered_not_paired_devices.insert(list[i].address);
UpdateBluetoothDeviceListHelper(
&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() {
if (!ash::Shell::GetInstance()->
system_tray_delegate()->ShouldShowSettings()) {
return;
}
// 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::ALIGN_LEFT,
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_;
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);
detailed_->Update();
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 ash