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