bluetooth_options_handler.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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 "chrome/browser/ui/webui/options/chromeos/bluetooth_options_handler.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/command_line.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "content/public/browser/web_ui.h"
15#include "device/bluetooth/bluetooth_adapter.h"
16#include "device/bluetooth/bluetooth_adapter_factory.h"
17#include "device/bluetooth/bluetooth_device.h"
18#include "grit/chromium_strings.h"
19#include "grit/generated_resources.h"
20#include "third_party/cros_system_api/dbus/service_constants.h"
21#include "ui/base/l10n/l10n_util.h"
22
23namespace {
24
25// |UpdateDeviceCallback| takes a variable length list as an argument. The
26// value stored in each list element is indicated by the following constants.
27const int kUpdateDeviceAddressIndex = 0;
28const int kUpdateDeviceCommandIndex = 1;
29const int kUpdateDeviceAuthTokenIndex = 2;
30
31// |UpdateDeviceCallback| provides a command value of one of the following
32// constants that indicates what update it is providing to us.
33const char kConnectCommand[] = "connect";
34const char kCancelCommand[] = "cancel";
35const char kAcceptCommand[] = "accept";
36const char kRejectCommand[] = "reject";
37const char kDisconnectCommand[] = "disconnect";
38const char kForgetCommand[] = "forget";
39
40// |SendDeviceNotification| may include a pairing parameter whose value
41// is one of the following constants instructing the UI to perform a certain
42// action.
43const char kStartConnecting[] = "bluetoothStartConnecting";
44const char kEnterPinCode[] = "bluetoothEnterPinCode";
45const char kEnterPasskey[] = "bluetoothEnterPasskey";
46const char kRemotePinCode[] = "bluetoothRemotePinCode";
47const char kRemotePasskey[] = "bluetoothRemotePasskey";
48const char kConfirmPasskey[] = "bluetoothConfirmPasskey";
49
50}  // namespace
51
52namespace chromeos {
53namespace options {
54
55BluetoothOptionsHandler::BluetoothOptionsHandler() : discovering_(false),
56                                                     weak_ptr_factory_(this) {
57}
58
59BluetoothOptionsHandler::~BluetoothOptionsHandler() {
60  if (discovering_) {
61    adapter_->StopDiscovering(
62        base::Bind(&base::DoNothing),
63        base::Bind(&base::DoNothing));
64    discovering_ = false;
65  }
66  if (adapter_.get())
67    adapter_->RemoveObserver(this);
68}
69
70void BluetoothOptionsHandler::GetLocalizedValues(
71    DictionaryValue* localized_strings) {
72  DCHECK(localized_strings);
73
74  static OptionsStringResource resources[] = {
75    { "bluetooth", IDS_OPTIONS_SETTINGS_SECTION_TITLE_BLUETOOTH },
76    { "disableBluetooth", IDS_OPTIONS_SETTINGS_BLUETOOTH_DISABLE },
77    { "enableBluetooth", IDS_OPTIONS_SETTINGS_BLUETOOTH_ENABLE },
78    { "addBluetoothDevice", IDS_OPTIONS_SETTINGS_ADD_BLUETOOTH_DEVICE },
79    { "bluetoothAddDeviceTitle",
80        IDS_OPTIONS_SETTINGS_BLUETOOTH_ADD_DEVICE_TITLE },
81    { "bluetoothOptionsPageTabTitle",
82        IDS_OPTIONS_SETTINGS_BLUETOOTH_ADD_DEVICE_TITLE },
83    { "findBluetoothDevices", IDS_OPTIONS_SETTINGS_FIND_BLUETOOTH_DEVICES },
84    { "bluetoothNoDevices", IDS_OPTIONS_SETTINGS_BLUETOOTH_NO_DEVICES },
85    { "bluetoothNoDevicesFound",
86        IDS_OPTIONS_SETTINGS_BLUETOOTH_NO_DEVICES_FOUND },
87    {"bluetoothScanning", IDS_OPTIONS_SETTINGS_BLUETOOTH_SCANNING },
88    {"bluetoothDeviceConnected", IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECTED },
89    { "bluetoothDeviceNotConnected",
90        IDS_OPTIONS_SETTINGS_BLUETOOTH_NOT_CONNECTED },
91    { "bluetoothConnectDevice", IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT },
92    { "bluetoothDisconnectDevice", IDS_OPTIONS_SETTINGS_BLUETOOTH_DISCONNECT },
93    { "bluetoothForgetDevice", IDS_OPTIONS_SETTINGS_BLUETOOTH_FORGET },
94    { "bluetoothCancel", IDS_OPTIONS_SETTINGS_BLUETOOTH_CANCEL },
95    { "bluetoothEnterKey", IDS_OPTIONS_SETTINGS_BLUETOOTH_ENTER_KEY },
96    { "bluetoothDismissError", IDS_OPTIONS_SETTINGS_BLUETOOTH_DISMISS_ERROR },
97
98    // Device connecting and pairing.
99    { "bluetoothStartConnecting",
100        IDS_OPTIONS_SETTINGS_BLUETOOTH_START_CONNECTING },
101    { "bluetoothAcceptPasskey",
102        IDS_OPTIONS_SETTINGS_BLUETOOTH_ACCEPT_PASSKEY },
103    { "bluetoothRejectPasskey",
104        IDS_OPTIONS_SETTINGS_BLUETOOTH_REJECT_PASSKEY },
105    { "bluetoothEnterPinCode",
106        IDS_OPTIONS_SETTINGS_BLUETOOTH_ENTER_PIN_CODE_REQUEST },
107    { "bluetoothEnterPasskey",
108        IDS_OPTIONS_SETTINGS_BLUETOOTH_ENTER_PASSKEY_REQUEST },
109    { "bluetoothRemotePinCode",
110        IDS_OPTIONS_SETTINGS_BLUETOOTH_REMOTE_PIN_CODE_REQUEST },
111    { "bluetoothRemotePasskey",
112        IDS_OPTIONS_SETTINGS_BLUETOOTH_REMOTE_PASSKEY_REQUEST },
113    { "bluetoothConfirmPasskey",
114        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONFIRM_PASSKEY_REQUEST },
115
116    // Error messages.
117    { "bluetoothStartDiscoveryFailed",
118        IDS_OPTIONS_SETTINGS_BLUETOOTH_START_DISCOVERY_FAILED },
119    { "bluetoothStopDiscoveryFailed",
120        IDS_OPTIONS_SETTINGS_BLUETOOTH_STOP_DISCOVERY_FAILED },
121    { "bluetoothChangePowerFailed",
122        IDS_OPTIONS_SETTINGS_BLUETOOTH_CHANGE_POWER_FAILED },
123    { "bluetoothConnectUnknownError",
124        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_UNKNOWN_ERROR },
125    { "bluetoothConnectInProgress",
126        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_IN_PROGRESS },
127    { "bluetoothConnectFailed",
128        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_FAILED },
129    { "bluetoothConnectAuthFailed",
130        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_AUTH_FAILED },
131    { "bluetoothConnectAuthCanceled",
132        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_AUTH_CANCELED },
133    { "bluetoothConnectAuthRejected",
134        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_AUTH_REJECTED },
135    { "bluetoothConnectAuthTimeout",
136        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_AUTH_TIMEOUT },
137    { "bluetoothConnectUnsupportedDevice",
138        IDS_OPTIONS_SETTINGS_BLUETOOTH_CONNECT_UNSUPPORTED_DEVICE },
139    { "bluetoothDisconnectFailed",
140        IDS_OPTIONS_SETTINGS_BLUETOOTH_DISCONNECT_FAILED },
141    { "bluetoothForgetFailed",
142        IDS_OPTIONS_SETTINGS_BLUETOOTH_FORGET_FAILED }};
143
144  RegisterStrings(localized_strings, resources, arraysize(resources));
145}
146
147// TODO(kevers): Reorder methods to match ordering in the header file.
148
149void BluetoothOptionsHandler::AdapterPresentChanged(
150    device::BluetoothAdapter* adapter,
151    bool present) {
152  DCHECK(adapter == adapter_.get());
153  if (present) {
154    web_ui()->CallJavascriptFunction(
155        "options.BrowserOptions.showBluetoothSettings");
156
157    // Update the checkbox and visibility based on the powered state of the
158    // new adapter.
159    AdapterPoweredChanged(adapter_.get(), adapter_->IsPowered());
160  } else {
161    web_ui()->CallJavascriptFunction(
162        "options.BrowserOptions.hideBluetoothSettings");
163  }
164}
165
166void BluetoothOptionsHandler::AdapterPoweredChanged(
167    device::BluetoothAdapter* adapter,
168    bool powered) {
169  DCHECK(adapter == adapter_.get());
170  base::FundamentalValue checked(powered);
171  web_ui()->CallJavascriptFunction(
172      "options.BrowserOptions.setBluetoothState", checked);
173}
174
175void BluetoothOptionsHandler::RegisterMessages() {
176  web_ui()->RegisterMessageCallback("bluetoothEnableChange",
177      base::Bind(&BluetoothOptionsHandler::EnableChangeCallback,
178                 base::Unretained(this)));
179  web_ui()->RegisterMessageCallback("findBluetoothDevices",
180      base::Bind(&BluetoothOptionsHandler::FindDevicesCallback,
181                 base::Unretained(this)));
182  web_ui()->RegisterMessageCallback("updateBluetoothDevice",
183      base::Bind(&BluetoothOptionsHandler::UpdateDeviceCallback,
184                 base::Unretained(this)));
185  web_ui()->RegisterMessageCallback("stopBluetoothDeviceDiscovery",
186      base::Bind(&BluetoothOptionsHandler::StopDiscoveryCallback,
187                 base::Unretained(this)));
188  web_ui()->RegisterMessageCallback("getPairedBluetoothDevices",
189      base::Bind(&BluetoothOptionsHandler::GetPairedDevicesCallback,
190                 base::Unretained(this)));
191}
192
193void BluetoothOptionsHandler::InitializeHandler() {
194  device::BluetoothAdapterFactory::GetAdapter(
195      base::Bind(&BluetoothOptionsHandler::InitializeAdapter,
196                 weak_ptr_factory_.GetWeakPtr()));
197}
198
199void BluetoothOptionsHandler::InitializePage() {
200  // Show or hide the bluetooth settings and update the checkbox based
201  // on the current present/powered state.
202  AdapterPresentChanged(adapter_.get(), adapter_->IsPresent());
203  // Automatically start device discovery if the "Add Bluetooth Device"
204  // overlay is visible.
205  web_ui()->CallJavascriptFunction(
206      "options.BluetoothOptions.updateDiscovery");
207}
208
209void BluetoothOptionsHandler::InitializeAdapter(
210    scoped_refptr<device::BluetoothAdapter> adapter) {
211  adapter_ = adapter;
212  CHECK(adapter_);
213  adapter_->AddObserver(this);
214}
215
216void BluetoothOptionsHandler::EnableChangeCallback(
217    const ListValue* args) {
218  bool bluetooth_enabled;
219  args->GetBoolean(0, &bluetooth_enabled);
220
221  adapter_->SetPowered(bluetooth_enabled,
222                       base::Bind(&base::DoNothing),
223                       base::Bind(&BluetoothOptionsHandler::EnableChangeError,
224                                  weak_ptr_factory_.GetWeakPtr()));
225}
226
227void BluetoothOptionsHandler::EnableChangeError() {
228  VLOG(1) << "Failed to change power state.";
229  ReportError("bluetoothChangePowerFailed", std::string());
230}
231
232void BluetoothOptionsHandler::FindDevicesCallback(
233    const ListValue* args) {
234  if (!discovering_) {
235    discovering_ = true;
236    adapter_->StartDiscovering(
237        base::Bind(&base::DoNothing),
238        base::Bind(&BluetoothOptionsHandler::FindDevicesError,
239                   weak_ptr_factory_.GetWeakPtr()));
240  }
241}
242
243void BluetoothOptionsHandler::FindDevicesError() {
244  VLOG(1) << "Failed to start discovery.";
245  ReportError("bluetoothStartDiscoveryFailed", std::string());
246}
247
248void BluetoothOptionsHandler::UpdateDeviceCallback(
249    const ListValue* args) {
250  std::string address;
251  args->GetString(kUpdateDeviceAddressIndex, &address);
252
253  device::BluetoothDevice* device = adapter_->GetDevice(address);
254  if (!device)
255    return;
256
257  std::string command;
258  args->GetString(kUpdateDeviceCommandIndex, &command);
259
260  if (command == kConnectCommand) {
261    int size = args->GetSize();
262    if (size > kUpdateDeviceAuthTokenIndex) {
263      // PIN code or Passkey entry during the pairing process.
264      std::string auth_token;
265      args->GetString(kUpdateDeviceAuthTokenIndex, &auth_token);
266
267      if (device->ExpectingPinCode()) {
268        DeviceConnecting(device);
269        // PIN Code is an array of 1 to 16 8-bit bytes, the usual
270        // interpretation, and the one shared by BlueZ, is a UTF-8 string
271        // of as many characters that will fit in that space, thus we
272        // can use the auth token from JavaScript unmodified.
273        VLOG(1) << "PIN Code supplied: " << address << ": " << auth_token;
274        device->SetPinCode(auth_token);
275      } else if (device->ExpectingPasskey()) {
276        DeviceConnecting(device);
277        // Passkey is a numeric in the range 0-999999, in this case the
278        // JavaScript code should have ensured the auth token string only
279        // contains digits so a simple conversion is sufficient. In the
280        // failure case, just use 0 since that's the most likely Passkey
281        // anyway, and if it's refused the device will request a new one.
282        unsigned passkey = 0;
283        base::StringToUint(auth_token, &passkey);
284
285        VLOG(1) << "Passkey supplied: " << address << ": " << passkey;
286        device->SetPasskey(passkey);
287      } else {
288        LOG(WARNING) << "Auth token supplied after pairing ended: " << address
289                     << ": " << auth_token;
290      }
291    } else {
292      // Connection request.
293      VLOG(1) << "Connect: " << address;
294      device->Connect(
295          this,
296          base::Bind(&base::DoNothing),
297          base::Bind(&BluetoothOptionsHandler::ConnectError,
298                     weak_ptr_factory_.GetWeakPtr(),
299                     device->address()));
300    }
301  } else if (command == kCancelCommand) {
302    // Cancel pairing.
303    VLOG(1) << "Cancel pairing: " << address;
304    device->CancelPairing();
305  } else if (command == kAcceptCommand) {
306    // Confirm displayed Passkey.
307    VLOG(1) << "Confirm pairing: " << address;
308    device->ConfirmPairing();
309  } else if (command == kRejectCommand) {
310    // Reject displayed Passkey.
311    VLOG(1) << "Reject pairing: " << address;
312    device->RejectPairing();
313  } else if (command == kDisconnectCommand) {
314    // Disconnect from device.
315    VLOG(1) << "Disconnect device: " << address;
316    device->Disconnect(
317        base::Bind(&base::DoNothing),
318        base::Bind(&BluetoothOptionsHandler::DisconnectError,
319                   weak_ptr_factory_.GetWeakPtr(),
320                   device->address()));
321  } else if (command == kForgetCommand) {
322    // Disconnect from device and delete pairing information.
323    VLOG(1) << "Forget device: " << address;
324    device->Forget(base::Bind(&BluetoothOptionsHandler::ForgetError,
325                              weak_ptr_factory_.GetWeakPtr(),
326                              device->address()));
327  } else {
328    LOG(WARNING) << "Unknown updateBluetoothDevice command: " << command;
329  }
330}
331
332void BluetoothOptionsHandler::ConnectError(
333    const std::string& address,
334    device::BluetoothDevice::ConnectErrorCode error_code) {
335  const char* error_name = NULL;
336
337  VLOG(1) << "Failed to connect to device: " << address;
338  switch (error_code) {
339    case device::BluetoothDevice::ERROR_UNKNOWN:
340      error_name = "bluetoothConnectUnknownError";
341      break;
342    case device::BluetoothDevice::ERROR_INPROGRESS:
343      error_name = "bluetoothConnectInProgress";
344      break;
345    case device::BluetoothDevice::ERROR_FAILED:
346      error_name = "bluetoothConnectFailed";
347      break;
348    case device::BluetoothDevice::ERROR_AUTH_FAILED:
349      error_name = "bluetoothConnectAuthFailed";
350      break;
351    case device::BluetoothDevice::ERROR_AUTH_CANCELED:
352      error_name = "bluetoothConnectAuthCanceled";
353      break;
354    case device::BluetoothDevice::ERROR_AUTH_REJECTED:
355      error_name = "bluetoothConnectAuthRejected";
356      break;
357    case device::BluetoothDevice::ERROR_AUTH_TIMEOUT:
358      error_name = "bluetoothConnectAuthTimeout";
359      break;
360    case device::BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
361      error_name = "bluetoothConnectUnsupportedDevice";
362      break;
363  }
364  // Report an error only if there's an error to report.
365  if (error_name)
366    ReportError(error_name, address);
367}
368
369void BluetoothOptionsHandler::DisconnectError(const std::string& address) {
370  VLOG(1) << "Failed to disconnect from device: " << address;
371  ReportError("bluetoothDisconnectFailed", address);
372}
373
374void BluetoothOptionsHandler::ForgetError(const std::string& address) {
375  VLOG(1) << "Failed to disconnect and unpair device: " << address;
376  ReportError("bluetoothForgetFailed", address);
377}
378
379void BluetoothOptionsHandler::StopDiscoveryCallback(
380    const ListValue* args) {
381  if (discovering_) {
382    adapter_->StopDiscovering(
383        base::Bind(&base::DoNothing),
384        base::Bind(&BluetoothOptionsHandler::StopDiscoveryError,
385                   weak_ptr_factory_.GetWeakPtr()));
386    discovering_ = false;
387  }
388}
389
390void BluetoothOptionsHandler::StopDiscoveryError() {
391  VLOG(1) << "Failed to stop discovery.";
392  ReportError("bluetoothStopDiscoveryFailed", std::string());
393}
394
395void BluetoothOptionsHandler::GetPairedDevicesCallback(
396    const ListValue* args) {
397  device::BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
398
399  for (device::BluetoothAdapter::DeviceList::iterator iter = devices.begin();
400       iter != devices.end(); ++iter)
401    SendDeviceNotification(*iter, NULL);
402}
403
404void BluetoothOptionsHandler::SendDeviceNotification(
405    const device::BluetoothDevice* device,
406    base::DictionaryValue* params) {
407  base::DictionaryValue js_properties;
408  js_properties.SetString("name", device->GetName());
409  js_properties.SetString("address", device->address());
410  js_properties.SetBoolean("paired", device->IsPaired());
411  js_properties.SetBoolean("bonded", device->IsBonded());
412  js_properties.SetBoolean("connected", device->IsConnected());
413  js_properties.SetBoolean("connectable", device->IsConnectable());
414  if (params)
415    js_properties.MergeDictionary(params);
416  web_ui()->CallJavascriptFunction(
417      "options.BrowserOptions.addBluetoothDevice",
418      js_properties);
419}
420
421void BluetoothOptionsHandler::RequestPinCode(device::BluetoothDevice* device) {
422  DictionaryValue params;
423  params.SetString("pairing", kEnterPinCode);
424  SendDeviceNotification(device, &params);
425}
426
427void BluetoothOptionsHandler::RequestPasskey(device::BluetoothDevice* device) {
428  DictionaryValue params;
429  params.SetString("pairing", kEnterPasskey);
430  SendDeviceNotification(device, &params);
431}
432
433void BluetoothOptionsHandler::DisplayPinCode(device::BluetoothDevice* device,
434                                             const std::string& pincode) {
435  DictionaryValue params;
436  params.SetString("pairing", kRemotePinCode);
437  params.SetString("pincode", pincode);
438  SendDeviceNotification(device, &params);
439}
440
441void BluetoothOptionsHandler::DisplayPasskey(device::BluetoothDevice* device,
442                                             uint32 passkey) {
443  DictionaryValue params;
444  params.SetString("pairing", kRemotePasskey);
445  params.SetInteger("passkey", passkey);
446  SendDeviceNotification(device, &params);
447}
448
449void BluetoothOptionsHandler::ConfirmPasskey(device::BluetoothDevice* device,
450                                             uint32 passkey) {
451  DictionaryValue params;
452  params.SetString("pairing", kConfirmPasskey);
453  params.SetInteger("passkey", passkey);
454  SendDeviceNotification(device, &params);
455}
456
457void BluetoothOptionsHandler::DismissDisplayOrConfirm() {
458  web_ui()->CallJavascriptFunction(
459      "options.BluetoothPairing.dismissDialog");
460}
461
462void BluetoothOptionsHandler::ReportError(
463    const std::string& error,
464    const std::string& address) {
465  base::DictionaryValue properties;
466  properties.SetString("label", error);
467  properties.SetString("address", address);
468  web_ui()->CallJavascriptFunction(
469      "options.BluetoothPairing.showMessage",
470      properties);
471}
472
473void BluetoothOptionsHandler::DeviceAdded(device::BluetoothAdapter* adapter,
474                                          device::BluetoothDevice* device) {
475  DCHECK(adapter == adapter_.get());
476  DCHECK(device);
477  SendDeviceNotification(device, NULL);
478}
479
480void BluetoothOptionsHandler::DeviceChanged(device::BluetoothAdapter* adapter,
481                                            device::BluetoothDevice* device) {
482  DCHECK(adapter == adapter_.get());
483  DCHECK(device);
484  SendDeviceNotification(device, NULL);
485}
486
487void BluetoothOptionsHandler::DeviceRemoved(device::BluetoothAdapter* adapter,
488                                            device::BluetoothDevice* device) {
489  DCHECK(adapter == adapter_.get());
490  DCHECK(device);
491
492  base::StringValue address(device->address());
493  web_ui()->CallJavascriptFunction(
494      "options.BrowserOptions.removeBluetoothDevice",
495      address);
496}
497
498void BluetoothOptionsHandler::DeviceConnecting(
499    device::BluetoothDevice* device) {
500  DCHECK(device);
501  DictionaryValue params;
502  params.SetString("pairing", kStartConnecting);
503  SendDeviceNotification(device, &params);
504}
505
506}  // namespace options
507}  // namespace chromeos
508