hid_detection_screen_handler.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 "chrome/browser/ui/webui/chromeos/login/hid_detection_screen_handler.h" 6 7#include "base/bind.h" 8#include "base/compiler_specific.h" 9#include "base/macros.h" 10#include "base/metrics/histogram.h" 11#include "base/prefs/pref_service.h" 12#include "base/strings/string16.h" 13#include "base/strings/string_number_conversions.h" 14#include "base/strings/utf_string_conversions.h" 15#include "chrome/browser/browser_process.h" 16#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" 17#include "chrome/common/pref_names.h" 18#include "device/bluetooth/bluetooth_adapter_factory.h" 19#include "grit/chromium_strings.h" 20#include "grit/generated_resources.h" 21#include "ui/base/l10n/l10n_util.h" 22 23namespace { 24 25const char kJsScreenPath[] = "login.HIDDetectionScreen"; 26 27// Variants of pairing state. 28const char kRemotePinCode[] = "bluetoothRemotePinCode"; 29const char kRemotePasskey[] = "bluetoothRemotePasskey"; 30 31// Possible ui-states for device-blocks. 32const char kSearchingState[] = "searching"; 33const char kUSBConnectedState[] = "connected"; 34const char kBTPairedState[] = "paired"; 35const char kBTPairingState[] = "pairing"; 36// Special state for notifications that don't switch ui-state, but add info. 37const char kBTUpdateState[] = "update"; 38 39// Names of possible arguments used for ui update. 40const char kPincodeArgName[] = "pincode"; 41const char kDeviceNameArgName[] = "name"; 42const char kLabelArgName[] = "keyboard-label"; 43 44// Standard length of pincode for pairing BT keyboards. 45const int kPincodeLength = 6; 46 47bool DeviceIsPointing(device::BluetoothDevice::DeviceType device_type) { 48 return device_type == device::BluetoothDevice::DEVICE_MOUSE || 49 device_type == device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO || 50 device_type == device::BluetoothDevice::DEVICE_TABLET; 51} 52 53bool DeviceIsPointing(const device::InputServiceLinux::InputDeviceInfo& info) { 54 return info.is_mouse || info.is_touchpad || info.is_touchscreen || 55 info.is_tablet; 56} 57 58bool DeviceIsKeyboard(device::BluetoothDevice::DeviceType device_type) { 59 return device_type == device::BluetoothDevice::DEVICE_KEYBOARD || 60 device_type == device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO; 61} 62 63} // namespace 64 65namespace chromeos { 66 67HIDDetectionScreenHandler::HIDDetectionScreenHandler( 68 CoreOobeActor* core_oobe_actor) 69 : BaseScreenHandler(kJsScreenPath), 70 delegate_(NULL), 71 core_oobe_actor_(core_oobe_actor), 72 show_on_init_(false), 73 mouse_is_pairing_(false), 74 keyboard_is_pairing_(false), 75 switch_on_adapter_when_ready_(false), 76 first_time_screen_show_(true), 77 weak_ptr_factory_(this) { 78} 79 80HIDDetectionScreenHandler::~HIDDetectionScreenHandler() { 81 if (adapter_.get()) 82 adapter_->RemoveObserver(this); 83 input_service_proxy_.RemoveObserver(this); 84 if (delegate_) 85 delegate_->OnActorDestroyed(this); 86} 87 88void HIDDetectionScreenHandler::OnStartDiscoverySession( 89 scoped_ptr<device::BluetoothDiscoverySession> discovery_session) { 90 VLOG(1) << "BT Discovery session started"; 91 discovery_session_ = discovery_session.Pass(); 92 UpdateDevices(); 93} 94 95void HIDDetectionScreenHandler::SetPoweredError() { 96 LOG(ERROR) << "Failed to power BT adapter"; 97} 98 99void HIDDetectionScreenHandler::FindDevicesError() { 100 VLOG(1) << "Failed to start Bluetooth discovery."; 101} 102 103void HIDDetectionScreenHandler::Show() { 104 if (!page_is_ready()) { 105 show_on_init_ = true; 106 return; 107 } 108 core_oobe_actor_->InitDemoModeDetection(); 109 input_service_proxy_.AddObserver(this); 110 first_time_screen_show_ = true; 111 GetDevicesFirstTime(); 112 ShowScreen(OobeUI::kScreenHIDDetection, NULL); 113} 114 115void HIDDetectionScreenHandler::Hide() { 116 if (adapter_.get()) 117 adapter_->RemoveObserver(this); 118 input_service_proxy_.RemoveObserver(this); 119} 120 121void HIDDetectionScreenHandler::SetDelegate(Delegate* delegate) { 122 delegate_ = delegate; 123 if (page_is_ready()) 124 Initialize(); 125} 126 127void HIDDetectionScreenHandler::DeclareLocalizedValues( 128 LocalizedValuesBuilder* builder) { 129 builder->Add("hidDetectionContinue", IDS_HID_DETECTION_CONTINUE_BUTTON); 130 builder->Add("hidDetectionInvitation", IDS_HID_DETECTION_INVITATION_TEXT); 131 builder->Add("hidDetectionPrerequisites", 132 IDS_HID_DETECTION_PRECONDITION_TEXT); 133 builder->Add("hidDetectionMouseSearching", IDS_HID_DETECTION_SEARCHING_MOUSE); 134 builder->Add("hidDetectionKeyboardSearching", 135 IDS_HID_DETECTION_SEARCHING_KEYBOARD); 136 builder->Add("hidDetectionUSBMouseConnected", 137 IDS_HID_DETECTION_CONNECTED_USB_MOUSE); 138 builder->Add("hidDetectionUSBKeyboardConnected", 139 IDS_HID_DETECTION_CONNECTED_USB_KEYBOARD); 140 builder->Add("hidDetectionBTMousePaired", 141 IDS_HID_DETECTION_PAIRED_BLUETOOTH_MOUSE); 142 builder->Add("hidDetectionBTEnterKey", IDS_HID_DETECTION_BLUETOOTH_ENTER_KEY); 143} 144 145void HIDDetectionScreenHandler::Initialize() { 146 if (!page_is_ready() || !delegate_) 147 return; 148 149 device::BluetoothAdapterFactory::GetAdapter( 150 base::Bind(&HIDDetectionScreenHandler::InitializeAdapter, 151 weak_ptr_factory_.GetWeakPtr())); 152 153 if (show_on_init_) { 154 Show(); 155 show_on_init_ = false; 156 } 157} 158 159void HIDDetectionScreenHandler::RegisterMessages() { 160 AddCallback( 161 "HIDDetectionOnContinue", &HIDDetectionScreenHandler::HandleOnContinue); 162} 163 164void HIDDetectionScreenHandler::HandleOnContinue() { 165 if (!first_time_screen_show_) { 166 // Continue button pressed. 167 ContinueScenarioType scenario_type; 168 if (!pointing_device_id_.empty() && !keyboard_device_id_.empty()) 169 scenario_type = All_DEVICES_DETECTED; 170 else if (pointing_device_id_.empty()) 171 scenario_type = KEYBOARD_DEVICE_ONLY_DETECTED; 172 else 173 scenario_type = POINTING_DEVICE_ONLY_DETECTED; 174 175 UMA_HISTOGRAM_ENUMERATION( 176 "HIDDetection.OOBEDevicesDetectedOnContinuePressed", 177 scenario_type, 178 CONTINUE_SCENARIO_TYPE_SIZE); 179 } 180 core_oobe_actor_->StopDemoModeDetection(); 181 if (delegate_) 182 delegate_->OnExit(); 183} 184 185void HIDDetectionScreenHandler::InitializeAdapter( 186 scoped_refptr<device::BluetoothAdapter> adapter) { 187 adapter_ = adapter; 188 CHECK(adapter_.get()); 189 190 adapter_->AddObserver(this); 191 UpdateDevices(); 192} 193 194void HIDDetectionScreenHandler::StartBTDiscoverySession() { 195 adapter_->StartDiscoverySession( 196 base::Bind(&HIDDetectionScreenHandler::OnStartDiscoverySession, 197 weak_ptr_factory_.GetWeakPtr()), 198 base::Bind(&HIDDetectionScreenHandler::FindDevicesError, 199 weak_ptr_factory_.GetWeakPtr())); 200} 201 202void HIDDetectionScreenHandler::RequestPinCode( 203 device::BluetoothDevice* device) { 204 VLOG(1) << "RequestPinCode id = " << device->GetDeviceID() 205 << " name = " << device->GetName(); 206 device->CancelPairing(); 207} 208 209void HIDDetectionScreenHandler::RequestPasskey( 210 device::BluetoothDevice* device) { 211 VLOG(1) << "RequestPassKey id = " << device->GetDeviceID() 212 << " name = " << device->GetName(); 213 device->CancelPairing(); 214} 215 216void HIDDetectionScreenHandler::DisplayPinCode(device::BluetoothDevice* device, 217 const std::string& pincode) { 218 VLOG(1) << "DisplayPinCode id = " << device->GetDeviceID() 219 << " name = " << device->GetName(); 220 base::DictionaryValue params; 221 params.SetString("state", kBTPairingState); 222 params.SetString("pairing-state", kRemotePinCode); 223 params.SetString("pincode", pincode); 224 params.SetString(kDeviceNameArgName, device->GetName()); 225 SendKeyboardDeviceNotification(¶ms); 226} 227 228void HIDDetectionScreenHandler::DisplayPasskey( 229 device::BluetoothDevice* device, uint32 passkey) { 230 VLOG(1) << "DisplayPassKey id = " << device->GetDeviceID() 231 << " name = " << device->GetName(); 232 base::DictionaryValue params; 233 params.SetString("state", kBTPairingState); 234 params.SetString("pairing-state", kRemotePasskey); 235 params.SetInteger("passkey", passkey); 236 std::string pincode = base::UintToString(passkey); 237 pincode = std::string(kPincodeLength - pincode.length(), '0').append(pincode); 238 params.SetString("pincode", pincode); 239 params.SetString(kDeviceNameArgName, device->GetName()); 240 SendKeyboardDeviceNotification(¶ms); 241} 242 243void HIDDetectionScreenHandler::KeysEntered( 244 device::BluetoothDevice* device, uint32 entered) { 245 VLOG(1) << "Keys entered"; 246 base::DictionaryValue params; 247 params.SetString("state", kBTUpdateState); 248 params.SetInteger("keysEntered", entered); 249 SendKeyboardDeviceNotification(¶ms); 250} 251 252void HIDDetectionScreenHandler::ConfirmPasskey( 253 device::BluetoothDevice* device, uint32 passkey) { 254 VLOG(1) << "Confirm Passkey"; 255 device->CancelPairing(); 256} 257 258void HIDDetectionScreenHandler::AuthorizePairing( 259 device::BluetoothDevice* device) { 260 // There is never any circumstance where this will be called, since the 261 // HID detection screen handler will only be used for outgoing pairing 262 // requests, but play it safe. 263 VLOG(1) << "Authorize pairing"; 264 device->ConfirmPairing(); 265} 266 267void HIDDetectionScreenHandler::AdapterPresentChanged( 268 device::BluetoothAdapter* adapter, bool present) { 269 if (present && switch_on_adapter_when_ready_) { 270 adapter_->SetPowered( 271 true, 272 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession, 273 weak_ptr_factory_.GetWeakPtr()), 274 base::Bind(&HIDDetectionScreenHandler::SetPoweredError, 275 weak_ptr_factory_.GetWeakPtr())); 276 } 277} 278 279void HIDDetectionScreenHandler::TryPairingAsPointingDevice( 280 device::BluetoothDevice* device) { 281 if (pointing_device_id_.empty() && 282 DeviceIsPointing(device->GetDeviceType()) && 283 device->IsPairable() && !device->IsPaired() && !mouse_is_pairing_) { 284 ConnectBTDevice(device); 285 } 286} 287 288void HIDDetectionScreenHandler::TryPairingAsKeyboardDevice( 289 device::BluetoothDevice* device) { 290 if (keyboard_device_id_.empty() && 291 DeviceIsKeyboard(device->GetDeviceType()) && 292 device->IsPairable() && !device->IsPaired() && !keyboard_is_pairing_) { 293 ConnectBTDevice(device); 294 } 295} 296 297void HIDDetectionScreenHandler::DeviceAdded( 298 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) { 299 VLOG(1) << "BT input device added id = " << device->GetDeviceID() << 300 " name = " << device->GetName(); 301 TryPairingAsPointingDevice(device); 302 TryPairingAsKeyboardDevice(device); 303} 304 305void HIDDetectionScreenHandler::DeviceChanged( 306 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) { 307 VLOG(1) << "BT device changed id = " << device->GetDeviceID() << " name = " << 308 device->GetName(); 309 TryPairingAsPointingDevice(device); 310 TryPairingAsKeyboardDevice(device); 311} 312 313void HIDDetectionScreenHandler::DeviceRemoved( 314 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) { 315 VLOG(1) << "BT device removed id = " << device->GetDeviceID() << " name = " << 316 device->GetName(); 317} 318 319void HIDDetectionScreenHandler::OnInputDeviceAdded( 320 const InputDeviceInfo& info) { 321 VLOG(1) << "Input device added id = " << info.id << " name = " << info.name; 322 // TODO(merkulova): deal with all available device types, e.g. joystick. 323 if (!keyboard_device_id_.empty() && !pointing_device_id_.empty()) 324 return; 325 326 if (pointing_device_id_.empty() && DeviceIsPointing(info)) { 327 pointing_device_id_ = info.id; 328 pointing_device_name_ = info.name; 329 pointing_device_connect_type_ = info.type; 330 SendPointingDeviceNotification(); 331 } 332 if (keyboard_device_id_.empty() && info.is_keyboard) { 333 keyboard_device_id_ = info.id; 334 keyboard_device_name_ = info.name; 335 keyboard_device_connect_type_ = info.type; 336 SendKeyboardDeviceNotification(NULL); 337 } 338} 339 340void HIDDetectionScreenHandler::OnInputDeviceRemoved(const std::string& id) { 341 if (id == keyboard_device_id_) { 342 keyboard_device_id_.clear(); 343 keyboard_device_name_.clear(); 344 keyboard_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN; 345 SendKeyboardDeviceNotification(NULL); 346 UpdateDevices(); 347 } else if (id == pointing_device_id_) { 348 pointing_device_id_.clear(); 349 pointing_device_name_.clear(); 350 pointing_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN; 351 SendPointingDeviceNotification(); 352 UpdateDevices(); 353 } 354} 355 356// static 357void HIDDetectionScreenHandler::RegisterPrefs(PrefRegistrySimple* registry) { 358 registry->RegisterIntegerPref(prefs::kTimesHIDDialogShown, 0); 359} 360 361void HIDDetectionScreenHandler::GetDevicesFirstTime() { 362 input_service_proxy_.GetDevices( 363 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesListFirstTime, 364 weak_ptr_factory_.GetWeakPtr())); 365} 366 367void HIDDetectionScreenHandler::UpdateDevices() { 368 input_service_proxy_.GetDevices( 369 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesList, 370 weak_ptr_factory_.GetWeakPtr())); 371} 372 373void HIDDetectionScreenHandler::UpdateBTDevices() { 374 if (!adapter_ || !adapter_->IsPresent() || !adapter_->IsPowered()) 375 return; 376 377 // If no connected devices found as pointing device and keyboard, we try to 378 // connect some type-suitable active bluetooth device. 379 std::vector<device::BluetoothDevice*> bt_devices = adapter_->GetDevices(); 380 for (std::vector<device::BluetoothDevice*>::const_iterator it = 381 bt_devices.begin(); 382 it != bt_devices.end() && 383 (keyboard_device_id_.empty() || pointing_device_id_.empty()); 384 ++it) { 385 TryPairingAsPointingDevice(*it); 386 TryPairingAsKeyboardDevice(*it); 387 } 388} 389 390void HIDDetectionScreenHandler::ProcessConnectedDevicesList( 391 const std::vector<InputDeviceInfo>& devices) { 392 for (std::vector<InputDeviceInfo>::const_iterator it = devices.begin(); 393 it != devices.end() && 394 (pointing_device_id_.empty() || keyboard_device_id_.empty()); 395 ++it) { 396 if (pointing_device_id_.empty() && DeviceIsPointing(*it)) { 397 pointing_device_id_ = it->id; 398 pointing_device_name_ = it->name; 399 pointing_device_connect_type_ = it->type; 400 SendPointingDeviceNotification(); 401 } 402 if (keyboard_device_id_.empty() && it->is_keyboard) { 403 keyboard_device_id_ = it->id; 404 keyboard_device_name_ = it->name; 405 keyboard_device_connect_type_ = it->type; 406 SendKeyboardDeviceNotification(NULL); 407 } 408 } 409} 410 411void HIDDetectionScreenHandler::TryInitiateBTDevicesUpdate() { 412 if ((pointing_device_id_.empty() || keyboard_device_id_.empty()) && 413 adapter_) { 414 if (!adapter_->IsPresent()) { 415 // Switch on BT adapter later when it's available. 416 switch_on_adapter_when_ready_ = true; 417 } else if (!adapter_->IsPowered()) { 418 adapter_->SetPowered( 419 true, 420 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession, 421 weak_ptr_factory_.GetWeakPtr()), 422 base::Bind(&HIDDetectionScreenHandler::SetPoweredError, 423 weak_ptr_factory_.GetWeakPtr())); 424 } else { 425 UpdateBTDevices(); 426 } 427 } 428} 429 430void HIDDetectionScreenHandler::OnGetInputDevicesListFirstTime( 431 const std::vector<InputDeviceInfo>& devices) { 432 ProcessConnectedDevicesList(devices); 433 434 // Skip screen if both devices are present. 435 bool all_devices_autodetected = !pointing_device_id_.empty() && 436 !keyboard_device_id_.empty(); 437 UMA_HISTOGRAM_BOOLEAN("HIDDetection.OOBEDialogShown", 438 !all_devices_autodetected); 439 if (all_devices_autodetected) { 440 HandleOnContinue(); 441 return; 442 } 443 PrefService* local_state = g_browser_process->local_state(); 444 int num_of_times_dialog_was_shown = local_state->GetInteger( 445 prefs::kTimesHIDDialogShown); 446 local_state->SetInteger(prefs::kTimesHIDDialogShown, 447 num_of_times_dialog_was_shown + 1); 448 first_time_screen_show_ = false; 449 450 TryInitiateBTDevicesUpdate(); 451} 452 453void HIDDetectionScreenHandler::OnGetInputDevicesList( 454 const std::vector<InputDeviceInfo>& devices) { 455 ProcessConnectedDevicesList(devices); 456 TryInitiateBTDevicesUpdate(); 457} 458 459void HIDDetectionScreenHandler::ConnectBTDevice( 460 device::BluetoothDevice* device) { 461 if (!device->IsPairable() || device->IsPaired()) 462 return; 463 device::BluetoothDevice::DeviceType device_type = device->GetDeviceType(); 464 465 if (device_type == device::BluetoothDevice::DEVICE_MOUSE || 466 device_type == device::BluetoothDevice::DEVICE_TABLET) { 467 if (mouse_is_pairing_) 468 return; 469 mouse_is_pairing_ = true; 470 } else if (device_type == device::BluetoothDevice::DEVICE_KEYBOARD) { 471 if (keyboard_is_pairing_) 472 return; 473 keyboard_is_pairing_ = true; 474 } else if (device_type == 475 device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO) { 476 if (mouse_is_pairing_ || keyboard_is_pairing_) 477 return; 478 mouse_is_pairing_ = true; 479 keyboard_is_pairing_ = true; 480 } 481 device->Connect(this, 482 base::Bind(&HIDDetectionScreenHandler::BTConnected, 483 weak_ptr_factory_.GetWeakPtr(), device_type), 484 base::Bind(&HIDDetectionScreenHandler::BTConnectError, 485 weak_ptr_factory_.GetWeakPtr(), 486 device->GetAddress(), device_type)); 487} 488 489void HIDDetectionScreenHandler::BTConnected( 490 device::BluetoothDevice::DeviceType device_type) { 491 if (DeviceIsPointing(device_type)) 492 mouse_is_pairing_ = false; 493 if (DeviceIsKeyboard(device_type)) 494 keyboard_is_pairing_ = false; 495} 496 497void HIDDetectionScreenHandler::BTConnectError( 498 const std::string& address, 499 device::BluetoothDevice::DeviceType device_type, 500 device::BluetoothDevice::ConnectErrorCode error_code) { 501 LOG(WARNING) << "BTConnectError while connecting " << address 502 << " error code = " << error_code; 503 if (DeviceIsPointing(device_type)) 504 mouse_is_pairing_ = false; 505 if (DeviceIsKeyboard(device_type)) { 506 keyboard_is_pairing_ = false; 507 SendKeyboardDeviceNotification(NULL); 508 } 509 510 if (pointing_device_id_.empty() || keyboard_device_id_.empty()) 511 UpdateDevices(); 512} 513 514 515void HIDDetectionScreenHandler::SendPointingDeviceNotification() { 516 std::string state; 517 if (pointing_device_id_.empty()) 518 state = kSearchingState; 519 else if (pointing_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) 520 state = kBTPairedState; 521 else 522 state = kUSBConnectedState; 523 CallJS("setPointingDeviceState", state); 524} 525 526void HIDDetectionScreenHandler::SendKeyboardDeviceNotification( 527 base::DictionaryValue* params) { 528 base::DictionaryValue state_info; 529 if (params) 530 state_info.MergeDictionary(params); 531 532 base::string16 device_name; 533 if (!state_info.GetString(kDeviceNameArgName, &device_name)) { 534 device_name = l10n_util::GetStringUTF16( 535 IDS_HID_DETECTION_DEFAULT_KEYBOARD_NAME); 536 } 537 538 if (keyboard_device_id_.empty()) { 539 if (!state_info.HasKey("state")) { 540 state_info.SetString("state", kSearchingState); 541 } else if (state_info.HasKey(kPincodeArgName)) { 542 state_info.SetString( 543 kLabelArgName, 544 l10n_util::GetStringFUTF16( 545 IDS_HID_DETECTION_BLUETOOTH_REMOTE_PIN_CODE_REQUEST, 546 device_name)); 547 } 548 } else if (keyboard_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) { 549 state_info.SetString("state", kBTPairedState); 550 state_info.SetString( 551 kLabelArgName, 552 l10n_util::GetStringFUTF16( 553 IDS_HID_DETECTION_PAIRED_BLUETOOTH_KEYBOARD, 554 base::UTF8ToUTF16(keyboard_device_name_))); 555 } else { 556 state_info.SetString("state", kUSBConnectedState); 557 } 558 CallJS("setKeyboardDeviceState", state_info); 559} 560 561} // namespace chromeos 562