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