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.
5 #include "chrome/browser/ui/webui/chromeos/login/hid_detection_screen_handler.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"
27 const char kJsScreenPath[] = "login.HIDDetectionScreen";
29 // Variants of pairing state.
30 const char kRemotePinCode[] = "bluetoothRemotePinCode";
31 const char kRemotePasskey[] = "bluetoothRemotePasskey";
33 // Possible ui-states for device-blocks.
34 const char kSearchingState[] = "searching";
35 const char kUSBConnectedState[] = "connected";
36 const char kBTPairedState[] = "paired";
37 const char kBTPairingState[] = "pairing";
38 // Special state for notifications that don't switch ui-state, but add info.
39 const char kBTUpdateState[] = "update";
41 // Names of possible arguments used for ui update.
42 const char kPincodeArgName[] = "pincode";
43 const char kDeviceNameArgName[] = "name";
44 const char kLabelArgName[] = "keyboard-label";
46 // Standard length of pincode for pairing BT keyboards.
47 const int kPincodeLength = 6;
49 bool 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;
55 bool DeviceIsPointing(const device::InputServiceLinux::InputDeviceInfo& info) {
56 return info.is_mouse || info.is_touchpad || info.is_touchscreen ||
60 bool DeviceIsKeyboard(device::BluetoothDevice::DeviceType device_type) {
61 return device_type == device::BluetoothDevice::DEVICE_KEYBOARD ||
62 device_type == device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO;
69 HIDDetectionScreenHandler::HIDDetectionScreenHandler(
70 CoreOobeActor* core_oobe_actor)
71 : BaseScreenHandler(kJsScreenPath),
73 core_oobe_actor_(core_oobe_actor),
75 mouse_is_pairing_(false),
76 pointing_device_connect_type_(InputDeviceInfo::TYPE_UNKNOWN),
77 keyboard_is_pairing_(false),
78 keyboard_device_connect_type_(InputDeviceInfo::TYPE_UNKNOWN),
79 switch_on_adapter_when_ready_(false),
80 first_time_screen_show_(true),
81 weak_ptr_factory_(this) {
84 HIDDetectionScreenHandler::~HIDDetectionScreenHandler() {
85 adapter_initially_powered_.reset();
87 adapter_->RemoveObserver(this);
88 input_service_proxy_.RemoveObserver(this);
90 delegate_->OnActorDestroyed(this);
93 void HIDDetectionScreenHandler::OnStartDiscoverySession(
94 scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
95 VLOG(1) << "BT Discovery session started";
96 discovery_session_ = discovery_session.Pass();
100 void HIDDetectionScreenHandler::SetPoweredError() {
101 LOG(ERROR) << "Failed to power BT adapter";
104 void HIDDetectionScreenHandler::SetPoweredOffError() {
105 LOG(ERROR) << "Failed to power off BT adapter";
108 void HIDDetectionScreenHandler::FindDevicesError() {
109 VLOG(1) << "Failed to start Bluetooth discovery.";
112 void HIDDetectionScreenHandler::Show() {
113 if (!page_is_ready()) {
114 show_on_init_ = true;
117 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableDemoMode))
118 core_oobe_actor_->InitDemoModeDetection();
119 input_service_proxy_.AddObserver(this);
120 first_time_screen_show_ = true;
121 GetDevicesFirstTime();
122 ShowScreen(OobeUI::kScreenHIDDetection, NULL);
125 void HIDDetectionScreenHandler::Hide() {
127 adapter_->RemoveObserver(this);
128 input_service_proxy_.RemoveObserver(this);
131 void HIDDetectionScreenHandler::SetDelegate(Delegate* delegate) {
132 delegate_ = delegate;
137 void HIDDetectionScreenHandler::DeclareLocalizedValues(
138 LocalizedValuesBuilder* builder) {
139 builder->Add("hidDetectionContinue", IDS_HID_DETECTION_CONTINUE_BUTTON);
140 builder->Add("hidDetectionInvitation", IDS_HID_DETECTION_INVITATION_TEXT);
141 builder->Add("hidDetectionPrerequisites",
142 IDS_HID_DETECTION_PRECONDITION_TEXT);
143 builder->Add("hidDetectionMouseSearching", IDS_HID_DETECTION_SEARCHING_MOUSE);
144 builder->Add("hidDetectionKeyboardSearching",
145 IDS_HID_DETECTION_SEARCHING_KEYBOARD);
146 builder->Add("hidDetectionUSBMouseConnected",
147 IDS_HID_DETECTION_CONNECTED_USB_MOUSE);
148 builder->Add("hidDetectionUSBKeyboardConnected",
149 IDS_HID_DETECTION_CONNECTED_USB_KEYBOARD);
150 builder->Add("hidDetectionBTMousePaired",
151 IDS_HID_DETECTION_PAIRED_BLUETOOTH_MOUSE);
152 builder->Add("hidDetectionBTEnterKey", IDS_HID_DETECTION_BLUETOOTH_ENTER_KEY);
155 void HIDDetectionScreenHandler::Initialize() {
156 if (!page_is_ready() || !delegate_)
159 device::BluetoothAdapterFactory::GetAdapter(
160 base::Bind(&HIDDetectionScreenHandler::InitializeAdapter,
161 weak_ptr_factory_.GetWeakPtr()));
165 show_on_init_ = false;
169 void HIDDetectionScreenHandler::RegisterMessages() {
171 "HIDDetectionOnContinue", &HIDDetectionScreenHandler::HandleOnContinue);
174 void HIDDetectionScreenHandler::HandleOnContinue() {
175 if (!first_time_screen_show_) {
176 // Continue button pressed.
177 ContinueScenarioType scenario_type;
178 if (!pointing_device_id_.empty() && !keyboard_device_id_.empty())
179 scenario_type = All_DEVICES_DETECTED;
180 else if (pointing_device_id_.empty())
181 scenario_type = KEYBOARD_DEVICE_ONLY_DETECTED;
183 scenario_type = POINTING_DEVICE_ONLY_DETECTED;
185 UMA_HISTOGRAM_ENUMERATION(
186 "HIDDetection.OOBEDevicesDetectedOnContinuePressed",
188 CONTINUE_SCENARIO_TYPE_SIZE);
190 // Switch off BT adapter if it was off before the screen and no BT device
192 if (adapter_ && adapter_->IsPresent() && adapter_->IsPowered() &&
193 !(pointing_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH ||
194 keyboard_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) &&
195 adapter_initially_powered_ && !(*adapter_initially_powered_)) {
196 VLOG(1) << "Switching off BT adapter after HID OOBE screen as unused.";
197 adapter_->SetPowered(
199 base::Bind(&base::DoNothing),
200 base::Bind(&HIDDetectionScreenHandler::SetPoweredOffError,
201 weak_ptr_factory_.GetWeakPtr()));
204 core_oobe_actor_->StopDemoModeDetection();
209 void HIDDetectionScreenHandler::InitializeAdapter(
210 scoped_refptr<device::BluetoothAdapter> adapter) {
212 CHECK(adapter_.get());
214 adapter_->AddObserver(this);
218 void HIDDetectionScreenHandler::StartBTDiscoverySession() {
219 adapter_->StartDiscoverySession(
220 base::Bind(&HIDDetectionScreenHandler::OnStartDiscoverySession,
221 weak_ptr_factory_.GetWeakPtr()),
222 base::Bind(&HIDDetectionScreenHandler::FindDevicesError,
223 weak_ptr_factory_.GetWeakPtr()));
226 void HIDDetectionScreenHandler::RequestPinCode(
227 device::BluetoothDevice* device) {
228 VLOG(1) << "RequestPinCode id = " << device->GetDeviceID()
229 << " name = " << device->GetName();
230 device->CancelPairing();
233 void HIDDetectionScreenHandler::RequestPasskey(
234 device::BluetoothDevice* device) {
235 VLOG(1) << "RequestPassKey id = " << device->GetDeviceID()
236 << " name = " << device->GetName();
237 device->CancelPairing();
240 void HIDDetectionScreenHandler::DisplayPinCode(device::BluetoothDevice* device,
241 const std::string& pincode) {
242 VLOG(1) << "DisplayPinCode id = " << device->GetDeviceID()
243 << " name = " << device->GetName();
244 base::DictionaryValue params;
245 params.SetString("state", kBTPairingState);
246 params.SetString("pairing-state", kRemotePinCode);
247 params.SetString("pincode", pincode);
248 params.SetString(kDeviceNameArgName, device->GetName());
249 SendKeyboardDeviceNotification(¶ms);
252 void HIDDetectionScreenHandler::DisplayPasskey(
253 device::BluetoothDevice* device, uint32 passkey) {
254 VLOG(1) << "DisplayPassKey id = " << device->GetDeviceID()
255 << " name = " << device->GetName();
256 base::DictionaryValue params;
257 params.SetString("state", kBTPairingState);
258 params.SetString("pairing-state", kRemotePasskey);
259 params.SetInteger("passkey", passkey);
260 std::string pincode = base::UintToString(passkey);
261 pincode = std::string(kPincodeLength - pincode.length(), '0').append(pincode);
262 params.SetString("pincode", pincode);
263 params.SetString(kDeviceNameArgName, device->GetName());
264 SendKeyboardDeviceNotification(¶ms);
267 void HIDDetectionScreenHandler::KeysEntered(
268 device::BluetoothDevice* device, uint32 entered) {
269 VLOG(1) << "Keys entered";
270 base::DictionaryValue params;
271 params.SetString("state", kBTUpdateState);
272 params.SetInteger("keysEntered", entered);
273 SendKeyboardDeviceNotification(¶ms);
276 void HIDDetectionScreenHandler::ConfirmPasskey(
277 device::BluetoothDevice* device, uint32 passkey) {
278 VLOG(1) << "Confirm Passkey";
279 device->CancelPairing();
282 void HIDDetectionScreenHandler::AuthorizePairing(
283 device::BluetoothDevice* device) {
284 // There is never any circumstance where this will be called, since the
285 // HID detection screen handler will only be used for outgoing pairing
286 // requests, but play it safe.
287 VLOG(1) << "Authorize pairing";
288 device->ConfirmPairing();
291 void HIDDetectionScreenHandler::AdapterPresentChanged(
292 device::BluetoothAdapter* adapter, bool present) {
293 if (present && switch_on_adapter_when_ready_) {
294 VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
295 adapter_initially_powered_.reset(new bool(adapter_->IsPowered()));
296 adapter_->SetPowered(
298 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession,
299 weak_ptr_factory_.GetWeakPtr()),
300 base::Bind(&HIDDetectionScreenHandler::SetPoweredError,
301 weak_ptr_factory_.GetWeakPtr()));
305 void HIDDetectionScreenHandler::TryPairingAsPointingDevice(
306 device::BluetoothDevice* device) {
307 if (pointing_device_id_.empty() &&
308 DeviceIsPointing(device->GetDeviceType()) &&
309 device->IsPairable() && !device->IsPaired() && !mouse_is_pairing_) {
310 ConnectBTDevice(device);
314 void HIDDetectionScreenHandler::TryPairingAsKeyboardDevice(
315 device::BluetoothDevice* device) {
316 if (keyboard_device_id_.empty() &&
317 DeviceIsKeyboard(device->GetDeviceType()) &&
318 device->IsPairable() && !device->IsPaired() && !keyboard_is_pairing_) {
319 ConnectBTDevice(device);
323 void HIDDetectionScreenHandler::DeviceAdded(
324 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
325 VLOG(1) << "BT input device added id = " << device->GetDeviceID() <<
326 " name = " << device->GetName();
327 TryPairingAsPointingDevice(device);
328 TryPairingAsKeyboardDevice(device);
331 void HIDDetectionScreenHandler::DeviceChanged(
332 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
333 VLOG(1) << "BT device changed id = " << device->GetDeviceID() << " name = " <<
335 TryPairingAsPointingDevice(device);
336 TryPairingAsKeyboardDevice(device);
339 void HIDDetectionScreenHandler::DeviceRemoved(
340 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
341 VLOG(1) << "BT device removed id = " << device->GetDeviceID() << " name = " <<
345 void HIDDetectionScreenHandler::OnInputDeviceAdded(
346 const InputDeviceInfo& info) {
347 VLOG(1) << "Input device added id = " << info.id << " name = " << info.name;
348 // TODO(merkulova): deal with all available device types, e.g. joystick.
349 if (!keyboard_device_id_.empty() && !pointing_device_id_.empty())
352 if (pointing_device_id_.empty() && DeviceIsPointing(info)) {
353 pointing_device_id_ = info.id;
354 pointing_device_name_ = info.name;
355 pointing_device_connect_type_ = info.type;
356 SendPointingDeviceNotification();
358 if (keyboard_device_id_.empty() && info.is_keyboard) {
359 keyboard_device_id_ = info.id;
360 keyboard_device_name_ = info.name;
361 keyboard_device_connect_type_ = info.type;
362 SendKeyboardDeviceNotification(NULL);
366 void HIDDetectionScreenHandler::OnInputDeviceRemoved(const std::string& id) {
367 if (id == keyboard_device_id_) {
368 keyboard_device_id_.clear();
369 keyboard_device_name_.clear();
370 keyboard_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN;
371 SendKeyboardDeviceNotification(NULL);
373 } else if (id == pointing_device_id_) {
374 pointing_device_id_.clear();
375 pointing_device_name_.clear();
376 pointing_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN;
377 SendPointingDeviceNotification();
383 void HIDDetectionScreenHandler::RegisterPrefs(PrefRegistrySimple* registry) {
384 registry->RegisterIntegerPref(prefs::kTimesHIDDialogShown, 0);
387 void HIDDetectionScreenHandler::GetDevicesFirstTime() {
388 input_service_proxy_.GetDevices(
389 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesListFirstTime,
390 weak_ptr_factory_.GetWeakPtr()));
393 void HIDDetectionScreenHandler::UpdateDevices() {
394 input_service_proxy_.GetDevices(
395 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesList,
396 weak_ptr_factory_.GetWeakPtr()));
399 void HIDDetectionScreenHandler::UpdateBTDevices() {
400 if (!adapter_ || !adapter_->IsPresent() || !adapter_->IsPowered())
403 // If no connected devices found as pointing device and keyboard, we try to
404 // connect some type-suitable active bluetooth device.
405 std::vector<device::BluetoothDevice*> bt_devices = adapter_->GetDevices();
406 for (std::vector<device::BluetoothDevice*>::const_iterator it =
408 it != bt_devices.end() &&
409 (keyboard_device_id_.empty() || pointing_device_id_.empty());
411 TryPairingAsPointingDevice(*it);
412 TryPairingAsKeyboardDevice(*it);
416 void HIDDetectionScreenHandler::ProcessConnectedDevicesList(
417 const std::vector<InputDeviceInfo>& devices) {
418 for (std::vector<InputDeviceInfo>::const_iterator it = devices.begin();
419 it != devices.end() &&
420 (pointing_device_id_.empty() || keyboard_device_id_.empty());
422 if (pointing_device_id_.empty() && DeviceIsPointing(*it)) {
423 pointing_device_id_ = it->id;
424 pointing_device_name_ = it->name;
425 pointing_device_connect_type_ = it->type;
426 SendPointingDeviceNotification();
428 if (keyboard_device_id_.empty() && it->is_keyboard) {
429 keyboard_device_id_ = it->id;
430 keyboard_device_name_ = it->name;
431 keyboard_device_connect_type_ = it->type;
432 SendKeyboardDeviceNotification(NULL);
437 void HIDDetectionScreenHandler::TryInitiateBTDevicesUpdate() {
438 if ((pointing_device_id_.empty() || keyboard_device_id_.empty()) &&
440 if (!adapter_->IsPresent()) {
441 // Switch on BT adapter later when it's available.
442 switch_on_adapter_when_ready_ = true;
443 } else if (!adapter_->IsPowered()) {
444 VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
445 adapter_initially_powered_.reset(new bool(false));
446 adapter_->SetPowered(
448 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession,
449 weak_ptr_factory_.GetWeakPtr()),
450 base::Bind(&HIDDetectionScreenHandler::SetPoweredError,
451 weak_ptr_factory_.GetWeakPtr()));
458 void HIDDetectionScreenHandler::OnGetInputDevicesListFirstTime(
459 const std::vector<InputDeviceInfo>& devices) {
460 ProcessConnectedDevicesList(devices);
462 // Skip screen if both devices are present.
463 bool all_devices_autodetected = !pointing_device_id_.empty() &&
464 !keyboard_device_id_.empty();
465 UMA_HISTOGRAM_BOOLEAN("HIDDetection.OOBEDialogShown",
466 !all_devices_autodetected);
467 if (all_devices_autodetected) {
471 PrefService* local_state = g_browser_process->local_state();
472 int num_of_times_dialog_was_shown = local_state->GetInteger(
473 prefs::kTimesHIDDialogShown);
474 local_state->SetInteger(prefs::kTimesHIDDialogShown,
475 num_of_times_dialog_was_shown + 1);
476 first_time_screen_show_ = false;
478 TryInitiateBTDevicesUpdate();
481 void HIDDetectionScreenHandler::OnGetInputDevicesList(
482 const std::vector<InputDeviceInfo>& devices) {
483 ProcessConnectedDevicesList(devices);
484 TryInitiateBTDevicesUpdate();
487 void HIDDetectionScreenHandler::ConnectBTDevice(
488 device::BluetoothDevice* device) {
489 if (!device->IsPairable() || device->IsPaired())
491 device::BluetoothDevice::DeviceType device_type = device->GetDeviceType();
493 if (device_type == device::BluetoothDevice::DEVICE_MOUSE ||
494 device_type == device::BluetoothDevice::DEVICE_TABLET) {
495 if (mouse_is_pairing_)
497 mouse_is_pairing_ = true;
498 } else if (device_type == device::BluetoothDevice::DEVICE_KEYBOARD) {
499 if (keyboard_is_pairing_)
501 keyboard_is_pairing_ = true;
502 } else if (device_type ==
503 device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO) {
504 if (mouse_is_pairing_ || keyboard_is_pairing_)
506 mouse_is_pairing_ = true;
507 keyboard_is_pairing_ = true;
509 device->Connect(this,
510 base::Bind(&HIDDetectionScreenHandler::BTConnected,
511 weak_ptr_factory_.GetWeakPtr(), device_type),
512 base::Bind(&HIDDetectionScreenHandler::BTConnectError,
513 weak_ptr_factory_.GetWeakPtr(),
514 device->GetAddress(), device_type));
517 void HIDDetectionScreenHandler::BTConnected(
518 device::BluetoothDevice::DeviceType device_type) {
519 if (DeviceIsPointing(device_type))
520 mouse_is_pairing_ = false;
521 if (DeviceIsKeyboard(device_type))
522 keyboard_is_pairing_ = false;
525 void HIDDetectionScreenHandler::BTConnectError(
526 const std::string& address,
527 device::BluetoothDevice::DeviceType device_type,
528 device::BluetoothDevice::ConnectErrorCode error_code) {
529 LOG(WARNING) << "BTConnectError while connecting " << address
530 << " error code = " << error_code;
531 if (DeviceIsPointing(device_type))
532 mouse_is_pairing_ = false;
533 if (DeviceIsKeyboard(device_type)) {
534 keyboard_is_pairing_ = false;
535 SendKeyboardDeviceNotification(NULL);
538 if (pointing_device_id_.empty() || keyboard_device_id_.empty())
543 void HIDDetectionScreenHandler::SendPointingDeviceNotification() {
545 if (pointing_device_id_.empty())
546 state = kSearchingState;
547 else if (pointing_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH)
548 state = kBTPairedState;
550 state = kUSBConnectedState;
551 CallJS("setPointingDeviceState", state);
554 void HIDDetectionScreenHandler::SendKeyboardDeviceNotification(
555 base::DictionaryValue* params) {
556 base::DictionaryValue state_info;
558 state_info.MergeDictionary(params);
560 base::string16 device_name;
561 if (!state_info.GetString(kDeviceNameArgName, &device_name)) {
562 device_name = l10n_util::GetStringUTF16(
563 IDS_HID_DETECTION_DEFAULT_KEYBOARD_NAME);
566 if (keyboard_device_id_.empty()) {
567 if (!state_info.HasKey("state")) {
568 state_info.SetString("state", kSearchingState);
569 } else if (state_info.HasKey(kPincodeArgName)) {
570 state_info.SetString(
572 l10n_util::GetStringFUTF16(
573 IDS_HID_DETECTION_BLUETOOTH_REMOTE_PIN_CODE_REQUEST,
576 } else if (keyboard_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) {
577 state_info.SetString("state", kBTPairedState);
578 state_info.SetString(
580 l10n_util::GetStringFUTF16(
581 IDS_HID_DETECTION_PAIRED_BLUETOOTH_KEYBOARD,
582 base::UTF8ToUTF16(keyboard_device_name_)));
584 state_info.SetString("state", kUSBConnectedState);
586 CallJS("setKeyboardDeviceState", state_info);
589 } // namespace chromeos