blob: f3472cec0ed5ff316b3cad3529a1edce6b7abfa4 [file] [log] [blame]
// Copyright 2014 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/bluetooth_notification_controller.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/notification_utils.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/message_center/public/cpp/notification_types.h"
using device::BluetoothAdapter;
using device::BluetoothAdapterFactory;
using device::BluetoothDevice;
using message_center::MessageCenter;
using message_center::Notification;
namespace {
const char kNotifierBluetooth[] = "ash.bluetooth";
// Identifier for the discoverable notification.
const char kBluetoothDeviceDiscoverableNotificationId[] =
"chrome://settings/bluetooth/discoverable";
// Identifier for the pairing notification; the Bluetooth code ensures we
// only receive one pairing request at a time, so a single id is sufficient and
// means we "update" one notification if not handled rather than continually
// bugging the user.
const char kBluetoothDevicePairingNotificationId[] =
"chrome://settings/bluetooth/pairing";
// Identifier for the notification that a device has been paired with the
// system.
const char kBluetoothDevicePairedNotificationId[] =
"chrome://settings/bluetooth/paired";
// The BluetoothPairingNotificationDelegate handles user interaction with the
// pairing notification and sending the confirmation, rejection or cancellation
// back to the underlying device.
class BluetoothPairingNotificationDelegate
: public message_center::NotificationDelegate {
public:
BluetoothPairingNotificationDelegate(scoped_refptr<BluetoothAdapter> adapter,
const std::string& address);
protected:
~BluetoothPairingNotificationDelegate() override;
// message_center::NotificationDelegate overrides.
void Close(bool by_user) override;
void Click(const base::Optional<int>& button_index,
const base::Optional<base::string16>& reply) override;
private:
// Buttons that appear in notifications.
enum Button { BUTTON_ACCEPT, BUTTON_REJECT };
// Reference to the underlying Bluetooth Adapter, holding onto this
// reference ensures the adapter object doesn't go out of scope while we have
// a pending request and user interaction.
scoped_refptr<BluetoothAdapter> adapter_;
// Address of the device being paired.
const std::string address_;
DISALLOW_COPY_AND_ASSIGN(BluetoothPairingNotificationDelegate);
};
BluetoothPairingNotificationDelegate::BluetoothPairingNotificationDelegate(
scoped_refptr<BluetoothAdapter> adapter,
const std::string& address)
: adapter_(adapter), address_(address) {}
BluetoothPairingNotificationDelegate::~BluetoothPairingNotificationDelegate() =
default;
void BluetoothPairingNotificationDelegate::Close(bool by_user) {
VLOG(1) << "Pairing notification closed. by_user = " << by_user;
// Ignore notification closes generated as a result of pairing completion.
if (!by_user)
return;
// Cancel the pairing of the device, if the object still exists.
BluetoothDevice* device = adapter_->GetDevice(address_);
if (device)
device->CancelPairing();
}
void BluetoothPairingNotificationDelegate::Click(
const base::Optional<int>& button_index,
const base::Optional<base::string16>& reply) {
if (!button_index)
return;
VLOG(1) << "Pairing notification, button click: " << *button_index;
// If the device object still exists, send the appropriate response either
// confirming or rejecting the pairing.
BluetoothDevice* device = adapter_->GetDevice(address_);
if (device) {
switch (*button_index) {
case BUTTON_ACCEPT:
device->ConfirmPairing();
break;
case BUTTON_REJECT:
device->RejectPairing();
break;
}
}
// In any case, remove this pairing notification.
MessageCenter::Get()->RemoveNotification(
kBluetoothDevicePairingNotificationId, false /* by_user */);
}
} // namespace
namespace ash {
BluetoothNotificationController::BluetoothNotificationController()
: weak_ptr_factory_(this) {
BluetoothAdapterFactory::GetAdapter(
base::Bind(&BluetoothNotificationController::OnGetAdapter,
weak_ptr_factory_.GetWeakPtr()));
}
BluetoothNotificationController::~BluetoothNotificationController() {
if (adapter_.get()) {
adapter_->RemoveObserver(this);
adapter_->RemovePairingDelegate(this);
adapter_ = NULL;
}
}
void BluetoothNotificationController::AdapterDiscoverableChanged(
BluetoothAdapter* adapter,
bool discoverable) {
if (discoverable) {
NotifyAdapterDiscoverable();
} else {
// Clear any previous discoverable notification.
MessageCenter::Get()->RemoveNotification(
kBluetoothDeviceDiscoverableNotificationId, false /* by_user */);
}
}
void BluetoothNotificationController::DeviceAdded(BluetoothAdapter* adapter,
BluetoothDevice* device) {
// Add the new device to the list of currently paired devices; it doesn't
// receive a notification since it's assumed it was previously notified.
if (device->IsPaired())
paired_devices_.insert(device->GetAddress());
}
void BluetoothNotificationController::DeviceChanged(BluetoothAdapter* adapter,
BluetoothDevice* device) {
// If the device is already in the list of paired devices, then don't
// notify.
if (paired_devices_.find(device->GetAddress()) != paired_devices_.end())
return;
// Otherwise if it's marked as paired then it must be newly paired, so
// notify the user about that.
if (device->IsPaired()) {
paired_devices_.insert(device->GetAddress());
NotifyPairedDevice(device);
}
}
void BluetoothNotificationController::DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) {
paired_devices_.erase(device->GetAddress());
}
void BluetoothNotificationController::RequestPinCode(BluetoothDevice* device) {
// Cannot provide keyboard entry in a notification; these devices (old car
// audio systems for the most part) will need pairing to be initiated from
// the Chromebook.
device->CancelPairing();
}
void BluetoothNotificationController::RequestPasskey(BluetoothDevice* device) {
// Cannot provide keyboard entry in a notification; fortunately the spec
// doesn't allow for this to be an option when we're receiving the pairing
// request anyway.
device->CancelPairing();
}
void BluetoothNotificationController::DisplayPinCode(
BluetoothDevice* device,
const std::string& pincode) {
base::string16 message = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PINCODE,
device->GetNameForDisplay(), base::UTF8ToUTF16(pincode));
NotifyPairing(device, message, false);
}
void BluetoothNotificationController::DisplayPasskey(BluetoothDevice* device,
uint32_t passkey) {
base::string16 message = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PASSKEY,
device->GetNameForDisplay(),
base::UTF8ToUTF16(base::StringPrintf("%06i", passkey)));
NotifyPairing(device, message, false);
}
void BluetoothNotificationController::KeysEntered(BluetoothDevice* device,
uint32_t entered) {
// Ignored since we don't have CSS in the notification to update.
}
void BluetoothNotificationController::ConfirmPasskey(BluetoothDevice* device,
uint32_t passkey) {
base::string16 message = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_CONFIRM_PASSKEY,
device->GetNameForDisplay(),
base::UTF8ToUTF16(base::StringPrintf("%06i", passkey)));
NotifyPairing(device, message, true);
}
void BluetoothNotificationController::AuthorizePairing(
BluetoothDevice* device) {
base::string16 message = l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_BLUETOOTH_AUTHORIZE_PAIRING,
device->GetNameForDisplay());
NotifyPairing(device, message, true);
}
void BluetoothNotificationController::OnGetAdapter(
scoped_refptr<BluetoothAdapter> adapter) {
DCHECK(!adapter_.get());
adapter_ = adapter;
adapter_->AddObserver(this);
adapter_->AddPairingDelegate(this,
BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW);
// Notify a user if the adapter is already in the discoverable state.
if (adapter_->IsDiscoverable())
NotifyAdapterDiscoverable();
// Build a list of the currently paired devices; these don't receive
// notifications since it's assumed they were previously notified.
BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
for (BluetoothAdapter::DeviceList::const_iterator iter = devices.begin();
iter != devices.end(); ++iter) {
const BluetoothDevice* device = *iter;
if (device->IsPaired())
paired_devices_.insert(device->GetAddress());
}
}
void BluetoothNotificationController::NotifyAdapterDiscoverable() {
message_center::RichNotificationData optional;
std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE,
kBluetoothDeviceDiscoverableNotificationId, base::string16() /* title */,
l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERABLE,
base::UTF8ToUTF16(adapter_->GetName()),
base::UTF8ToUTF16(adapter_->GetAddress())),
base::string16() /* display source */, GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierBluetooth),
optional, nullptr, kNotificationBluetoothIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
MessageCenter::Get()->AddNotification(std::move(notification));
}
void BluetoothNotificationController::NotifyPairing(
BluetoothDevice* device,
const base::string16& message,
bool with_buttons) {
message_center::RichNotificationData optional;
if (with_buttons) {
optional.buttons.push_back(message_center::ButtonInfo(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_ACCEPT)));
optional.buttons.push_back(message_center::ButtonInfo(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_REJECT)));
}
std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE,
kBluetoothDevicePairingNotificationId, base::string16() /* title */,
message, base::string16() /* display source */, GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierBluetooth),
optional,
new BluetoothPairingNotificationDelegate(adapter_, device->GetAddress()),
kNotificationBluetoothIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
MessageCenter::Get()->AddNotification(std::move(notification));
}
void BluetoothNotificationController::NotifyPairedDevice(
BluetoothDevice* device) {
// Remove the currently presented pairing notification; since only one
// pairing request is queued at a time, this is guaranteed to be the device
// that just became paired.
MessageCenter::Get()->RemoveNotification(
kBluetoothDevicePairingNotificationId, false /* by_user */);
message_center::RichNotificationData optional;
std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE,
kBluetoothDevicePairedNotificationId, base::string16() /* title */,
l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIRED,
device->GetNameForDisplay()),
base::string16() /* display source */, GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierBluetooth),
optional, nullptr, kNotificationBluetoothIcon,
message_center::SystemNotificationWarningLevel::NORMAL);
MessageCenter::Get()->AddNotification(std::move(notification));
}
} // namespace ash