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, ¶ms); 425} 426 427void BluetoothOptionsHandler::RequestPasskey(device::BluetoothDevice* device) { 428 DictionaryValue params; 429 params.SetString("pairing", kEnterPasskey); 430 SendDeviceNotification(device, ¶ms); 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, ¶ms); 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, ¶ms); 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, ¶ms); 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, ¶ms); 504} 505 506} // namespace options 507} // namespace chromeos 508