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