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
BluetoothPairingNotificationDelegate(scoped_refptr<BluetoothAdapter> adapter,const std::string & address)86 BluetoothPairingNotificationDelegate::BluetoothPairingNotificationDelegate(
87 scoped_refptr<BluetoothAdapter> adapter,
88 const std::string& address)
89 : adapter_(adapter),
90 address_(address) {
91 }
92
~BluetoothPairingNotificationDelegate()93 BluetoothPairingNotificationDelegate::~BluetoothPairingNotificationDelegate() {
94 }
95
Display()96 void BluetoothPairingNotificationDelegate::Display() {
97 }
98
Error()99 void BluetoothPairingNotificationDelegate::Error() {
100 }
101
Close(bool by_user)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
HasClickedListener()114 bool BluetoothPairingNotificationDelegate::HasClickedListener() {
115 return false;
116 }
117
Click()118 void BluetoothPairingNotificationDelegate::Click() {
119 }
120
ButtonClick(int button_index)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
BluetoothNotificationController()147 BluetoothNotificationController::BluetoothNotificationController()
148 : weak_ptr_factory_(this) {
149 BluetoothAdapterFactory::GetAdapter(
150 base::Bind(&BluetoothNotificationController::OnGetAdapter,
151 weak_ptr_factory_.GetWeakPtr()));
152 }
153
~BluetoothNotificationController()154 BluetoothNotificationController::~BluetoothNotificationController() {
155 if (adapter_.get()) {
156 adapter_->RemoveObserver(this);
157 adapter_->RemovePairingDelegate(this);
158 adapter_ = NULL;
159 }
160 }
161
162
AdapterDiscoverableChanged(BluetoothAdapter * adapter,bool discoverable)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
DeviceAdded(BluetoothAdapter * adapter,BluetoothDevice * device)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
DeviceChanged(BluetoothAdapter * adapter,BluetoothDevice * device)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
DeviceRemoved(BluetoothAdapter * adapter,BluetoothDevice * device)198 void BluetoothNotificationController::DeviceRemoved(BluetoothAdapter* adapter,
199 BluetoothDevice* device) {
200 paired_devices_.erase(device->GetAddress());
201 }
202
203
RequestPinCode(BluetoothDevice * device)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
RequestPasskey(BluetoothDevice * device)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
DisplayPinCode(BluetoothDevice * device,const std::string & pincode)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
DisplayPasskey(BluetoothDevice * device,uint32 passkey)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
KeysEntered(BluetoothDevice * device,uint32 entered)238 void BluetoothNotificationController::KeysEntered(BluetoothDevice* device,
239 uint32 entered) {
240 // Ignored since we don't have CSS in the notification to update.
241 }
242
ConfirmPasskey(BluetoothDevice * device,uint32 passkey)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
AuthorizePairing(BluetoothDevice * device)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
OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter)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
NotifyAdapterDiscoverable()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
NotifyPairing(BluetoothDevice * device,const base::string16 & message,bool with_buttons)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
NotifyPairedDevice(BluetoothDevice * device)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