[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 1 | // Copyright 2014 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 | |
James Cook | a35a1e2 | 2017-04-08 02:33:08 | [diff] [blame^] | 5 | #include "ash/system/bluetooth/bluetooth_notification_controller.h" |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 6 | |
dcheng | a9454747 | 2016-04-08 08:41:11 | [diff] [blame] | 7 | #include <memory> |
dcheng | 2272495 | 2015-12-31 03:17:54 | [diff] [blame] | 8 | #include <utility> |
| 9 | |
James Cook | 6def4d9d | 2017-03-05 22:13:47 | [diff] [blame] | 10 | #include "ash/common/system/system_notifier.h" |
thakis | 9532e89 | 2017-02-22 18:46:26 | [diff] [blame] | 11 | #include "ash/resources/grit/ash_resources.h" |
jamescook | e45f811 | 2017-03-02 16:45:42 | [diff] [blame] | 12 | #include "ash/strings/grit/ash_strings.h" |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 13 | #include "base/bind.h" |
| 14 | #include "base/callback.h" |
| 15 | #include "base/logging.h" |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 16 | #include "base/strings/stringprintf.h" |
| 17 | #include "base/strings/utf_string_conversions.h" |
| 18 | #include "device/bluetooth/bluetooth_adapter_factory.h" |
| 19 | #include "device/bluetooth/bluetooth_device.h" |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 20 | #include "ui/base/l10n/l10n_util.h" |
| 21 | #include "ui/base/resource/resource_bundle.h" |
| 22 | #include "ui/message_center/message_center.h" |
| 23 | #include "ui/message_center/notification.h" |
| 24 | #include "ui/message_center/notification_delegate.h" |
| 25 | #include "ui/message_center/notification_types.h" |
| 26 | |
| 27 | using device::BluetoothAdapter; |
| 28 | using device::BluetoothAdapterFactory; |
| 29 | using device::BluetoothDevice; |
| 30 | using message_center::Notification; |
| 31 | |
| 32 | namespace { |
| 33 | |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 34 | // Identifier for the discoverable notification. |
| 35 | const char kBluetoothDeviceDiscoverableNotificationId[] = |
| 36 | "chrome://settings/bluetooth/discoverable"; |
| 37 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 38 | // Identifier for the pairing notification; the Bluetooth code ensures we |
| 39 | // only receive one pairing request at a time, so a single id is sufficient and |
| 40 | // means we "update" one notification if not handled rather than continually |
| 41 | // bugging the user. |
| 42 | const char kBluetoothDevicePairingNotificationId[] = |
| 43 | "chrome://settings/bluetooth/pairing"; |
| 44 | |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 45 | // Identifier for the notification that a device has been paired with the |
| 46 | // system. |
| 47 | const char kBluetoothDevicePairedNotificationId[] = |
| 48 | "chrome://settings/bluetooth/paired"; |
| 49 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 50 | // The BluetoothPairingNotificationDelegate handles user interaction with the |
| 51 | // pairing notification and sending the confirmation, rejection or cancellation |
| 52 | // back to the underlying device. |
| 53 | class BluetoothPairingNotificationDelegate |
| 54 | : public message_center::NotificationDelegate { |
| 55 | public: |
| 56 | BluetoothPairingNotificationDelegate(scoped_refptr<BluetoothAdapter> adapter, |
| 57 | const std::string& address); |
| 58 | |
| 59 | protected: |
dcheng | 222b9c7 | 2015-01-16 00:48:01 | [diff] [blame] | 60 | ~BluetoothPairingNotificationDelegate() override; |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 61 | |
| 62 | // message_center::NotificationDelegate overrides. |
peter | cf29ede8 | 2014-10-30 23:19:19 | [diff] [blame] | 63 | void Close(bool by_user) override; |
| 64 | void ButtonClick(int button_index) override; |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 65 | |
| 66 | private: |
| 67 | // Buttons that appear in notifications. |
jamescook | b8dcef52 | 2016-06-25 14:42:55 | [diff] [blame] | 68 | enum Button { BUTTON_ACCEPT, BUTTON_REJECT }; |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 69 | |
| 70 | // Reference to the underlying Bluetooth Adapter, holding onto this |
| 71 | // reference ensures the adapter object doesn't go out of scope while we have |
| 72 | // a pending request and user interaction. |
| 73 | scoped_refptr<BluetoothAdapter> adapter_; |
| 74 | |
| 75 | // Address of the device being paired. |
| 76 | const std::string address_; |
| 77 | |
| 78 | DISALLOW_COPY_AND_ASSIGN(BluetoothPairingNotificationDelegate); |
| 79 | }; |
| 80 | |
| 81 | BluetoothPairingNotificationDelegate::BluetoothPairingNotificationDelegate( |
| 82 | scoped_refptr<BluetoothAdapter> adapter, |
| 83 | const std::string& address) |
jamescook | b8dcef52 | 2016-06-25 14:42:55 | [diff] [blame] | 84 | : adapter_(adapter), address_(address) {} |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 85 | |
jamescook | b8dcef52 | 2016-06-25 14:42:55 | [diff] [blame] | 86 | BluetoothPairingNotificationDelegate::~BluetoothPairingNotificationDelegate() {} |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 87 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 88 | void BluetoothPairingNotificationDelegate::Close(bool by_user) { |
| 89 | VLOG(1) << "Pairing notification closed. by_user = " << by_user; |
| 90 | // Ignore notification closes generated as a result of pairing completion. |
| 91 | if (!by_user) |
| 92 | return; |
| 93 | |
| 94 | // Cancel the pairing of the device, if the object still exists. |
| 95 | BluetoothDevice* device = adapter_->GetDevice(address_); |
| 96 | if (device) |
| 97 | device->CancelPairing(); |
| 98 | } |
| 99 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 100 | void BluetoothPairingNotificationDelegate::ButtonClick(int button_index) { |
| 101 | VLOG(1) << "Pairing notification, button click: " << button_index; |
| 102 | // If the device object still exists, send the appropriate response either |
| 103 | // confirming or rejecting the pairing. |
| 104 | BluetoothDevice* device = adapter_->GetDevice(address_); |
| 105 | if (device) { |
| 106 | switch (button_index) { |
| 107 | case BUTTON_ACCEPT: |
| 108 | device->ConfirmPairing(); |
| 109 | break; |
| 110 | case BUTTON_REJECT: |
| 111 | device->RejectPairing(); |
| 112 | break; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | // In any case, remove this pairing notification. |
| 117 | message_center::MessageCenter::Get()->RemoveNotification( |
| 118 | kBluetoothDevicePairingNotificationId, false /* by_user */); |
| 119 | } |
| 120 | |
| 121 | } // namespace |
| 122 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 123 | namespace ash { |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 124 | |
| 125 | BluetoothNotificationController::BluetoothNotificationController() |
| 126 | : weak_ptr_factory_(this) { |
| 127 | BluetoothAdapterFactory::GetAdapter( |
| 128 | base::Bind(&BluetoothNotificationController::OnGetAdapter, |
| 129 | weak_ptr_factory_.GetWeakPtr())); |
| 130 | } |
| 131 | |
| 132 | BluetoothNotificationController::~BluetoothNotificationController() { |
| 133 | if (adapter_.get()) { |
| 134 | adapter_->RemoveObserver(this); |
| 135 | adapter_->RemovePairingDelegate(this); |
| 136 | adapter_ = NULL; |
| 137 | } |
| 138 | } |
| 139 | |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 140 | void BluetoothNotificationController::AdapterDiscoverableChanged( |
| 141 | BluetoothAdapter* adapter, |
| 142 | bool discoverable) { |
| 143 | if (discoverable) { |
| 144 | NotifyAdapterDiscoverable(); |
| 145 | } else { |
| 146 | // Clear any previous discoverable notification. |
| 147 | message_center::MessageCenter::Get()->RemoveNotification( |
| 148 | kBluetoothDeviceDiscoverableNotificationId, false /* by_user */); |
| 149 | } |
| 150 | } |
| 151 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 152 | void BluetoothNotificationController::DeviceAdded(BluetoothAdapter* adapter, |
| 153 | BluetoothDevice* device) { |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 154 | // Add the new device to the list of currently paired devices; it doesn't |
| 155 | // receive a notification since it's assumed it was previously notified. |
| 156 | if (device->IsPaired()) |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 157 | paired_devices_.insert(device->GetAddress()); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 158 | } |
| 159 | |
| 160 | void BluetoothNotificationController::DeviceChanged(BluetoothAdapter* adapter, |
| 161 | BluetoothDevice* device) { |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 162 | // If the device is already in the list of paired devices, then don't |
| 163 | // notify. |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 164 | if (paired_devices_.find(device->GetAddress()) != paired_devices_.end()) |
| 165 | return; |
| 166 | |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 167 | // Otherwise if it's marked as paired then it must be newly paired, so |
| 168 | // notify the user about that. |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 169 | if (device->IsPaired()) { |
| 170 | paired_devices_.insert(device->GetAddress()); |
| 171 | NotifyPairedDevice(device); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | void BluetoothNotificationController::DeviceRemoved(BluetoothAdapter* adapter, |
| 176 | BluetoothDevice* device) { |
| 177 | paired_devices_.erase(device->GetAddress()); |
| 178 | } |
| 179 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 180 | void BluetoothNotificationController::RequestPinCode(BluetoothDevice* device) { |
| 181 | // Cannot provide keyboard entry in a notification; these devices (old car |
| 182 | // audio systems for the most part) will need pairing to be initiated from |
| 183 | // the Chromebook. |
| 184 | device->CancelPairing(); |
| 185 | } |
| 186 | |
| 187 | void BluetoothNotificationController::RequestPasskey(BluetoothDevice* device) { |
| 188 | // Cannot provide keyboard entry in a notification; fortunately the spec |
| 189 | // doesn't allow for this to be an option when we're receiving the pairing |
| 190 | // request anyway. |
| 191 | device->CancelPairing(); |
| 192 | } |
| 193 | |
| 194 | void BluetoothNotificationController::DisplayPinCode( |
| 195 | BluetoothDevice* device, |
| 196 | const std::string& pincode) { |
| 197 | base::string16 message = l10n_util::GetStringFUTF16( |
scheib | e5ccec5c | 2016-06-02 20:42:49 | [diff] [blame] | 198 | IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PINCODE, |
| 199 | device->GetNameForDisplay(), base::UTF8ToUTF16(pincode)); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 200 | |
| 201 | NotifyPairing(device, message, false); |
| 202 | } |
| 203 | |
| 204 | void BluetoothNotificationController::DisplayPasskey(BluetoothDevice* device, |
avi | db567a8a | 2015-12-20 17:07:24 | [diff] [blame] | 205 | uint32_t passkey) { |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 206 | base::string16 message = l10n_util::GetStringFUTF16( |
scheib | e5ccec5c | 2016-06-02 20:42:49 | [diff] [blame] | 207 | IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PASSKEY, |
| 208 | device->GetNameForDisplay(), |
| 209 | base::UTF8ToUTF16(base::StringPrintf("%06i", passkey))); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 210 | |
| 211 | NotifyPairing(device, message, false); |
| 212 | } |
| 213 | |
| 214 | void BluetoothNotificationController::KeysEntered(BluetoothDevice* device, |
avi | db567a8a | 2015-12-20 17:07:24 | [diff] [blame] | 215 | uint32_t entered) { |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 216 | // Ignored since we don't have CSS in the notification to update. |
| 217 | } |
| 218 | |
| 219 | void BluetoothNotificationController::ConfirmPasskey(BluetoothDevice* device, |
avi | db567a8a | 2015-12-20 17:07:24 | [diff] [blame] | 220 | uint32_t passkey) { |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 221 | base::string16 message = l10n_util::GetStringFUTF16( |
scheib | e5ccec5c | 2016-06-02 20:42:49 | [diff] [blame] | 222 | IDS_ASH_STATUS_TRAY_BLUETOOTH_CONFIRM_PASSKEY, |
| 223 | device->GetNameForDisplay(), |
| 224 | base::UTF8ToUTF16(base::StringPrintf("%06i", passkey))); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 225 | |
| 226 | NotifyPairing(device, message, true); |
| 227 | } |
| 228 | |
| 229 | void BluetoothNotificationController::AuthorizePairing( |
| 230 | BluetoothDevice* device) { |
| 231 | base::string16 message = l10n_util::GetStringFUTF16( |
scheib | e5ccec5c | 2016-06-02 20:42:49 | [diff] [blame] | 232 | IDS_ASH_STATUS_TRAY_BLUETOOTH_AUTHORIZE_PAIRING, |
| 233 | device->GetNameForDisplay()); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 234 | |
| 235 | NotifyPairing(device, message, true); |
| 236 | } |
| 237 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 238 | void BluetoothNotificationController::OnGetAdapter( |
| 239 | scoped_refptr<BluetoothAdapter> adapter) { |
| 240 | DCHECK(!adapter_.get()); |
| 241 | adapter_ = adapter; |
| 242 | adapter_->AddObserver(this); |
| 243 | adapter_->AddPairingDelegate(this, |
| 244 | BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW); |
| 245 | |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 246 | // Notify a user if the adapter is already in the discoverable state. |
| 247 | if (adapter_->IsDiscoverable()) |
| 248 | NotifyAdapterDiscoverable(); |
| 249 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 250 | // Build a list of the currently paired devices; these don't receive |
| 251 | // notifications since it's assumed they were previously notified. |
| 252 | BluetoothAdapter::DeviceList devices = adapter_->GetDevices(); |
| 253 | for (BluetoothAdapter::DeviceList::const_iterator iter = devices.begin(); |
| 254 | iter != devices.end(); ++iter) { |
| 255 | const BluetoothDevice* device = *iter; |
| 256 | if (device->IsPaired()) |
| 257 | paired_devices_.insert(device->GetAddress()); |
| 258 | } |
| 259 | } |
| 260 | |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 261 | void BluetoothNotificationController::NotifyAdapterDiscoverable() { |
| 262 | message_center::RichNotificationData optional; |
| 263 | |
| 264 | ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 265 | |
dcheng | a9454747 | 2016-04-08 08:41:11 | [diff] [blame] | 266 | std::unique_ptr<Notification> notification(new Notification( |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 267 | message_center::NOTIFICATION_TYPE_SIMPLE, |
miguelg | 53a6dde5 | 2015-08-20 09:00:30 | [diff] [blame] | 268 | kBluetoothDeviceDiscoverableNotificationId, base::string16() /* title */, |
| 269 | l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERABLE, |
| 270 | base::UTF8ToUTF16(adapter_->GetName()), |
| 271 | base::UTF8ToUTF16(adapter_->GetAddress())), |
[email protected] | 21a83847 | 2014-06-11 13:40:37 | [diff] [blame] | 272 | bundle.GetImageNamed(IDR_AURA_NOTIFICATION_BLUETOOTH), |
miguelg | 53a6dde5 | 2015-08-20 09:00:30 | [diff] [blame] | 273 | base::string16() /* display source */, GURL(), |
| 274 | message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| 275 | system_notifier::kNotifierBluetooth), |
| 276 | optional, NULL)); |
dcheng | 2272495 | 2015-12-31 03:17:54 | [diff] [blame] | 277 | message_center::MessageCenter::Get()->AddNotification( |
| 278 | std::move(notification)); |
[email protected] | 7abf2fc | 2014-03-05 00:08:25 | [diff] [blame] | 279 | } |
| 280 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 281 | void BluetoothNotificationController::NotifyPairing( |
| 282 | BluetoothDevice* device, |
| 283 | const base::string16& message, |
| 284 | bool with_buttons) { |
| 285 | message_center::RichNotificationData optional; |
| 286 | if (with_buttons) { |
| 287 | optional.buttons.push_back(message_center::ButtonInfo( |
jamescook | b8dcef52 | 2016-06-25 14:42:55 | [diff] [blame] | 288 | l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_ACCEPT))); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 289 | optional.buttons.push_back(message_center::ButtonInfo( |
jamescook | b8dcef52 | 2016-06-25 14:42:55 | [diff] [blame] | 290 | l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_REJECT))); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 291 | } |
| 292 | |
| 293 | ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 294 | |
dcheng | a9454747 | 2016-04-08 08:41:11 | [diff] [blame] | 295 | std::unique_ptr<Notification> notification(new Notification( |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 296 | message_center::NOTIFICATION_TYPE_SIMPLE, |
miguelg | 53a6dde5 | 2015-08-20 09:00:30 | [diff] [blame] | 297 | kBluetoothDevicePairingNotificationId, base::string16() /* title */, |
| 298 | message, bundle.GetImageNamed(IDR_AURA_NOTIFICATION_BLUETOOTH), |
| 299 | base::string16() /* display source */, GURL(), |
| 300 | message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| 301 | system_notifier::kNotifierBluetooth), |
| 302 | optional, new BluetoothPairingNotificationDelegate( |
| 303 | adapter_, device->GetAddress()))); |
dcheng | 2272495 | 2015-12-31 03:17:54 | [diff] [blame] | 304 | message_center::MessageCenter::Get()->AddNotification( |
| 305 | std::move(notification)); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 306 | } |
| 307 | |
| 308 | void BluetoothNotificationController::NotifyPairedDevice( |
| 309 | BluetoothDevice* device) { |
| 310 | // Remove the currently presented pairing notification; since only one |
| 311 | // pairing request is queued at a time, this is guaranteed to be the device |
| 312 | // that just became paired. |
| 313 | message_center::MessageCenter::Get()->RemoveNotification( |
| 314 | kBluetoothDevicePairingNotificationId, false /* by_user */); |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 315 | |
| 316 | message_center::RichNotificationData optional; |
| 317 | |
| 318 | ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 319 | |
dcheng | a9454747 | 2016-04-08 08:41:11 | [diff] [blame] | 320 | std::unique_ptr<Notification> notification(new Notification( |
[email protected] | 98bd3f1 | 2014-03-04 21:59:18 | [diff] [blame] | 321 | message_center::NOTIFICATION_TYPE_SIMPLE, |
miguelg | 53a6dde5 | 2015-08-20 09:00:30 | [diff] [blame] | 322 | kBluetoothDevicePairedNotificationId, base::string16() /* title */, |
| 323 | l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIRED, |
scheib | e5ccec5c | 2016-06-02 20:42:49 | [diff] [blame] | 324 | device->GetNameForDisplay()), |
[email protected] | 21a83847 | 2014-06-11 13:40:37 | [diff] [blame] | 325 | bundle.GetImageNamed(IDR_AURA_NOTIFICATION_BLUETOOTH), |
miguelg | 53a6dde5 | 2015-08-20 09:00:30 | [diff] [blame] | 326 | base::string16() /* display source */, GURL(), |
| 327 | message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| 328 | system_notifier::kNotifierBluetooth), |
| 329 | optional, NULL)); |
dcheng | 2272495 | 2015-12-31 03:17:54 | [diff] [blame] | 330 | message_center::MessageCenter::Get()->AddNotification( |
| 331 | std::move(notification)); |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 332 | } |
| 333 | |
[email protected] | b314272 | 2014-03-04 06:59:50 | [diff] [blame] | 334 | } // namespace ash |