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
25using device::BluetoothAdapter;
26using device::BluetoothAdapterFactory;
27using device::BluetoothDevice;
28using message_center::Notification;
29
30namespace {
31
32// Identifier for the discoverable notification.
33const 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.
40const char kBluetoothDevicePairingNotificationId[] =
41    "chrome://settings/bluetooth/pairing";
42
43// Identifier for the notification that a device has been paired with the
44// system.
45const 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.
51class 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
86BluetoothPairingNotificationDelegate::BluetoothPairingNotificationDelegate(
87    scoped_refptr<BluetoothAdapter> adapter,
88    const std::string& address)
89    : adapter_(adapter),
90      address_(address) {
91}
92
93BluetoothPairingNotificationDelegate::~BluetoothPairingNotificationDelegate() {
94}
95
96void BluetoothPairingNotificationDelegate::Display() {
97}
98
99void BluetoothPairingNotificationDelegate::Error() {
100}
101
102void 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
114bool BluetoothPairingNotificationDelegate::HasClickedListener() {
115  return false;
116}
117
118void BluetoothPairingNotificationDelegate::Click() {
119}
120
121void 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
145namespace ash {
146
147BluetoothNotificationController::BluetoothNotificationController()
148    : weak_ptr_factory_(this) {
149  BluetoothAdapterFactory::GetAdapter(
150      base::Bind(&BluetoothNotificationController::OnGetAdapter,
151                 weak_ptr_factory_.GetWeakPtr()));
152}
153
154BluetoothNotificationController::~BluetoothNotificationController() {
155  if (adapter_.get()) {
156    adapter_->RemoveObserver(this);
157    adapter_->RemovePairingDelegate(this);
158    adapter_ = NULL;
159  }
160}
161
162
163void 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
175void 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
183void 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
198void BluetoothNotificationController::DeviceRemoved(BluetoothAdapter* adapter,
199                                                    BluetoothDevice* device) {
200  paired_devices_.erase(device->GetAddress());
201}
202
203
204void 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
211void 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
218void 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
228void 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
238void BluetoothNotificationController::KeysEntered(BluetoothDevice* device,
239                                                  uint32 entered) {
240  // Ignored since we don't have CSS in the notification to update.
241}
242
243void 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
253void 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
263void 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
287void 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
310void 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
342void 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