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 "chrome/grit/generated_resources.h"
20 #include "chromeos/chromeos_switches.h"
21 #include "device/bluetooth/bluetooth_adapter_factory.h"
22 #include "ui/base/l10n/l10n_util.h"
26 const char kJsScreenPath[] = "login.HIDDetectionScreen";
28 // Variants of pairing state.
29 const char kRemotePinCode[] = "bluetoothRemotePinCode";
30 const char kRemotePasskey[] = "bluetoothRemotePasskey";
32 // Possible ui-states for device-blocks.
33 const char kSearchingState[] = "searching";
34 const char kUSBConnectedState[] = "connected";
35 const char kBTPairedState[] = "paired";
36 const char kBTPairingState[] = "pairing";
37 // Special state for notifications that don't switch ui-state, but add info.
38 const char kBTUpdateState[] = "update";
40 // Names of possible arguments used for ui update.
41 const char kPincodeArgName[] = "pincode";
42 const char kDeviceNameArgName[] = "name";
43 const char kLabelArgName[] = "keyboard-label";
45 // Standard length of pincode for pairing BT keyboards.
46 const int kPincodeLength = 6;
48 bool DeviceIsPointing(device::BluetoothDevice::DeviceType device_type) {
49 return device_type == device::BluetoothDevice::DEVICE_MOUSE ||
50 device_type == device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO ||
51 device_type == device::BluetoothDevice::DEVICE_TABLET;
54 bool DeviceIsPointing(const device::InputServiceLinux::InputDeviceInfo& info) {
55 return info.is_mouse || info.is_touchpad || info.is_touchscreen ||
59 bool DeviceIsKeyboard(device::BluetoothDevice::DeviceType device_type) {
60 return device_type == device::BluetoothDevice::DEVICE_KEYBOARD ||
61 device_type == device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO;
68 HIDDetectionScreenHandler::HIDDetectionScreenHandler(
69 CoreOobeActor* core_oobe_actor)
70 : BaseScreenHandler(kJsScreenPath),
72 core_oobe_actor_(core_oobe_actor),
74 mouse_is_pairing_(false),
75 pointing_device_connect_type_(InputDeviceInfo::TYPE_UNKNOWN),
76 keyboard_is_pairing_(false),
77 keyboard_device_connect_type_(InputDeviceInfo::TYPE_UNKNOWN),
78 switch_on_adapter_when_ready_(false),
79 weak_ptr_factory_(this) {
82 HIDDetectionScreenHandler::~HIDDetectionScreenHandler() {
83 adapter_initially_powered_.reset();
85 adapter_->RemoveObserver(this);
86 input_service_proxy_.RemoveObserver(this);
88 delegate_->OnActorDestroyed(this);
91 void HIDDetectionScreenHandler::OnStartDiscoverySession(
92 scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
93 VLOG(1) << "BT Discovery session started";
94 discovery_session_ = discovery_session.Pass();
98 void HIDDetectionScreenHandler::SetPoweredError() {
99 LOG(ERROR) << "Failed to power BT adapter";
102 void HIDDetectionScreenHandler::SetPoweredOffError() {
103 LOG(ERROR) << "Failed to power off BT adapter";
106 void HIDDetectionScreenHandler::FindDevicesError() {
107 VLOG(1) << "Failed to start Bluetooth discovery.";
110 void HIDDetectionScreenHandler::Show() {
111 if (!page_is_ready()) {
112 show_on_init_ = true;
115 core_oobe_actor_->InitDemoModeDetection();
116 input_service_proxy_.AddObserver(this);
119 PrefService* local_state = g_browser_process->local_state();
120 int num_of_times_dialog_was_shown = local_state->GetInteger(
121 prefs::kTimesHIDDialogShown);
122 local_state->SetInteger(prefs::kTimesHIDDialogShown,
123 num_of_times_dialog_was_shown + 1);
125 ShowScreen(OobeUI::kScreenHIDDetection, NULL);
126 if (!pointing_device_id_.empty())
127 SendPointingDeviceNotification();
128 if (!keyboard_device_id_.empty())
129 SendKeyboardDeviceNotification(NULL);
132 void HIDDetectionScreenHandler::Hide() {
134 adapter_->RemoveObserver(this);
135 input_service_proxy_.RemoveObserver(this);
138 void HIDDetectionScreenHandler::SetDelegate(Delegate* delegate) {
139 delegate_ = delegate;
144 void HIDDetectionScreenHandler::CheckIsScreenRequired(
145 const base::Callback<void(bool)>& on_check_done) {
146 input_service_proxy_.GetDevices(
147 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesListForCheck,
148 weak_ptr_factory_.GetWeakPtr(),
152 void HIDDetectionScreenHandler::DeclareLocalizedValues(
153 LocalizedValuesBuilder* builder) {
154 builder->Add("hidDetectionContinue", IDS_HID_DETECTION_CONTINUE_BUTTON);
155 builder->Add("hidDetectionInvitation", IDS_HID_DETECTION_INVITATION_TEXT);
156 builder->Add("hidDetectionPrerequisites",
157 IDS_HID_DETECTION_PRECONDITION_TEXT);
158 builder->Add("hidDetectionMouseSearching", IDS_HID_DETECTION_SEARCHING_MOUSE);
159 builder->Add("hidDetectionKeyboardSearching",
160 IDS_HID_DETECTION_SEARCHING_KEYBOARD);
161 builder->Add("hidDetectionUSBMouseConnected",
162 IDS_HID_DETECTION_CONNECTED_USB_MOUSE);
163 builder->Add("hidDetectionUSBKeyboardConnected",
164 IDS_HID_DETECTION_CONNECTED_USB_KEYBOARD);
165 builder->Add("hidDetectionBTMousePaired",
166 IDS_HID_DETECTION_PAIRED_BLUETOOTH_MOUSE);
167 builder->Add("hidDetectionBTEnterKey", IDS_HID_DETECTION_BLUETOOTH_ENTER_KEY);
170 void HIDDetectionScreenHandler::Initialize() {
171 if (!page_is_ready() || !delegate_)
174 device::BluetoothAdapterFactory::GetAdapter(
175 base::Bind(&HIDDetectionScreenHandler::InitializeAdapter,
176 weak_ptr_factory_.GetWeakPtr()));
180 show_on_init_ = false;
184 void HIDDetectionScreenHandler::RegisterMessages() {
186 "HIDDetectionOnContinue", &HIDDetectionScreenHandler::HandleOnContinue);
189 void HIDDetectionScreenHandler::HandleOnContinue() {
190 // Continue button pressed.
191 ContinueScenarioType scenario_type;
192 if (!pointing_device_id_.empty() && !keyboard_device_id_.empty())
193 scenario_type = All_DEVICES_DETECTED;
194 else if (pointing_device_id_.empty())
195 scenario_type = KEYBOARD_DEVICE_ONLY_DETECTED;
197 scenario_type = POINTING_DEVICE_ONLY_DETECTED;
199 UMA_HISTOGRAM_ENUMERATION(
200 "HIDDetection.OOBEDevicesDetectedOnContinuePressed",
202 CONTINUE_SCENARIO_TYPE_SIZE);
204 // Switch off BT adapter if it was off before the screen and no BT device
206 if (adapter_.get() && adapter_->IsPresent() && adapter_->IsPowered() &&
207 !(pointing_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH ||
208 keyboard_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) &&
209 adapter_initially_powered_ && !(*adapter_initially_powered_)) {
210 VLOG(1) << "Switching off BT adapter after HID OOBE screen as unused.";
211 adapter_->SetPowered(
213 base::Bind(&base::DoNothing),
214 base::Bind(&HIDDetectionScreenHandler::SetPoweredOffError,
215 weak_ptr_factory_.GetWeakPtr()));
218 core_oobe_actor_->StopDemoModeDetection();
223 void HIDDetectionScreenHandler::InitializeAdapter(
224 scoped_refptr<device::BluetoothAdapter> adapter) {
226 CHECK(adapter_.get());
228 adapter_->AddObserver(this);
232 void HIDDetectionScreenHandler::StartBTDiscoverySession() {
233 adapter_->StartDiscoverySession(
234 base::Bind(&HIDDetectionScreenHandler::OnStartDiscoverySession,
235 weak_ptr_factory_.GetWeakPtr()),
236 base::Bind(&HIDDetectionScreenHandler::FindDevicesError,
237 weak_ptr_factory_.GetWeakPtr()));
240 void HIDDetectionScreenHandler::RequestPinCode(
241 device::BluetoothDevice* device) {
242 VLOG(1) << "RequestPinCode id = " << device->GetDeviceID()
243 << " name = " << device->GetName();
244 device->CancelPairing();
247 void HIDDetectionScreenHandler::RequestPasskey(
248 device::BluetoothDevice* device) {
249 VLOG(1) << "RequestPassKey id = " << device->GetDeviceID()
250 << " name = " << device->GetName();
251 device->CancelPairing();
254 void HIDDetectionScreenHandler::DisplayPinCode(device::BluetoothDevice* device,
255 const std::string& pincode) {
256 VLOG(1) << "DisplayPinCode id = " << device->GetDeviceID()
257 << " name = " << device->GetName();
258 base::DictionaryValue params;
259 params.SetString("state", kBTPairingState);
260 params.SetString("pairing-state", kRemotePinCode);
261 params.SetString("pincode", pincode);
262 params.SetString(kDeviceNameArgName, device->GetName());
263 SendKeyboardDeviceNotification(¶ms);
266 void HIDDetectionScreenHandler::DisplayPasskey(
267 device::BluetoothDevice* device, uint32 passkey) {
268 VLOG(1) << "DisplayPassKey id = " << device->GetDeviceID()
269 << " name = " << device->GetName();
270 base::DictionaryValue params;
271 params.SetString("state", kBTPairingState);
272 params.SetString("pairing-state", kRemotePasskey);
273 params.SetInteger("passkey", passkey);
274 std::string pincode = base::UintToString(passkey);
275 pincode = std::string(kPincodeLength - pincode.length(), '0').append(pincode);
276 params.SetString("pincode", pincode);
277 params.SetString(kDeviceNameArgName, device->GetName());
278 SendKeyboardDeviceNotification(¶ms);
281 void HIDDetectionScreenHandler::KeysEntered(
282 device::BluetoothDevice* device, uint32 entered) {
283 VLOG(1) << "Keys entered";
284 base::DictionaryValue params;
285 params.SetString("state", kBTUpdateState);
286 params.SetInteger("keysEntered", entered);
287 SendKeyboardDeviceNotification(¶ms);
290 void HIDDetectionScreenHandler::ConfirmPasskey(
291 device::BluetoothDevice* device, uint32 passkey) {
292 VLOG(1) << "Confirm Passkey";
293 device->CancelPairing();
296 void HIDDetectionScreenHandler::AuthorizePairing(
297 device::BluetoothDevice* device) {
298 // There is never any circumstance where this will be called, since the
299 // HID detection screen handler will only be used for outgoing pairing
300 // requests, but play it safe.
301 VLOG(1) << "Authorize pairing";
302 device->ConfirmPairing();
305 void HIDDetectionScreenHandler::AdapterPresentChanged(
306 device::BluetoothAdapter* adapter, bool present) {
307 if (present && switch_on_adapter_when_ready_) {
308 VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
309 adapter_initially_powered_.reset(new bool(adapter_->IsPowered()));
310 adapter_->SetPowered(
312 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession,
313 weak_ptr_factory_.GetWeakPtr()),
314 base::Bind(&HIDDetectionScreenHandler::SetPoweredError,
315 weak_ptr_factory_.GetWeakPtr()));
319 void HIDDetectionScreenHandler::TryPairingAsPointingDevice(
320 device::BluetoothDevice* device) {
321 if (pointing_device_id_.empty() &&
322 DeviceIsPointing(device->GetDeviceType()) &&
323 device->IsPairable() && !device->IsPaired() && !mouse_is_pairing_) {
324 ConnectBTDevice(device);
328 void HIDDetectionScreenHandler::TryPairingAsKeyboardDevice(
329 device::BluetoothDevice* device) {
330 if (keyboard_device_id_.empty() &&
331 DeviceIsKeyboard(device->GetDeviceType()) &&
332 device->IsPairable() && !device->IsPaired() && !keyboard_is_pairing_) {
333 ConnectBTDevice(device);
337 void HIDDetectionScreenHandler::DeviceAdded(
338 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
339 VLOG(1) << "BT input device added id = " << device->GetDeviceID() <<
340 " name = " << device->GetName();
341 TryPairingAsPointingDevice(device);
342 TryPairingAsKeyboardDevice(device);
345 void HIDDetectionScreenHandler::DeviceChanged(
346 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
347 VLOG(1) << "BT device changed id = " << device->GetDeviceID() << " name = " <<
349 TryPairingAsPointingDevice(device);
350 TryPairingAsKeyboardDevice(device);
353 void HIDDetectionScreenHandler::DeviceRemoved(
354 device::BluetoothAdapter* adapter, device::BluetoothDevice* device) {
355 VLOG(1) << "BT device removed id = " << device->GetDeviceID() << " name = " <<
359 void HIDDetectionScreenHandler::OnInputDeviceAdded(
360 const InputDeviceInfo& info) {
361 VLOG(1) << "Input device added id = " << info.id << " name = " << info.name;
362 // TODO(merkulova): deal with all available device types, e.g. joystick.
363 if (!keyboard_device_id_.empty() && !pointing_device_id_.empty())
366 if (pointing_device_id_.empty() && DeviceIsPointing(info)) {
367 pointing_device_id_ = info.id;
368 pointing_device_name_ = info.name;
369 pointing_device_connect_type_ = info.type;
370 SendPointingDeviceNotification();
372 if (keyboard_device_id_.empty() && info.is_keyboard) {
373 keyboard_device_id_ = info.id;
374 keyboard_device_name_ = info.name;
375 keyboard_device_connect_type_ = info.type;
376 SendKeyboardDeviceNotification(NULL);
380 void HIDDetectionScreenHandler::OnInputDeviceRemoved(const std::string& id) {
381 if (id == keyboard_device_id_) {
382 keyboard_device_id_.clear();
383 keyboard_device_name_.clear();
384 keyboard_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN;
385 SendKeyboardDeviceNotification(NULL);
387 } else if (id == pointing_device_id_) {
388 pointing_device_id_.clear();
389 pointing_device_name_.clear();
390 pointing_device_connect_type_ = InputDeviceInfo::TYPE_UNKNOWN;
391 SendPointingDeviceNotification();
397 void HIDDetectionScreenHandler::RegisterPrefs(PrefRegistrySimple* registry) {
398 registry->RegisterIntegerPref(prefs::kTimesHIDDialogShown, 0);
401 void HIDDetectionScreenHandler::UpdateDevices() {
402 input_service_proxy_.GetDevices(
403 base::Bind(&HIDDetectionScreenHandler::OnGetInputDevicesList,
404 weak_ptr_factory_.GetWeakPtr()));
407 void HIDDetectionScreenHandler::UpdateBTDevices() {
408 if (!adapter_.get() || !adapter_->IsPresent() || !adapter_->IsPowered())
411 // If no connected devices found as pointing device and keyboard, we try to
412 // connect some type-suitable active bluetooth device.
413 std::vector<device::BluetoothDevice*> bt_devices = adapter_->GetDevices();
414 for (std::vector<device::BluetoothDevice*>::const_iterator it =
416 it != bt_devices.end() &&
417 (keyboard_device_id_.empty() || pointing_device_id_.empty());
419 TryPairingAsPointingDevice(*it);
420 TryPairingAsKeyboardDevice(*it);
424 void HIDDetectionScreenHandler::ProcessConnectedDevicesList(
425 const std::vector<InputDeviceInfo>& devices) {
426 for (std::vector<InputDeviceInfo>::const_iterator it = devices.begin();
427 it != devices.end() &&
428 (pointing_device_id_.empty() || keyboard_device_id_.empty());
430 if (pointing_device_id_.empty() && DeviceIsPointing(*it)) {
431 pointing_device_id_ = it->id;
432 pointing_device_name_ = it->name;
433 pointing_device_connect_type_ = it->type;
435 SendPointingDeviceNotification();
437 if (keyboard_device_id_.empty() && it->is_keyboard) {
438 keyboard_device_id_ = it->id;
439 keyboard_device_name_ = it->name;
440 keyboard_device_connect_type_ = it->type;
442 SendKeyboardDeviceNotification(NULL);
447 void HIDDetectionScreenHandler::TryInitiateBTDevicesUpdate() {
448 if ((pointing_device_id_.empty() || keyboard_device_id_.empty()) &&
450 if (!adapter_->IsPresent()) {
451 // Switch on BT adapter later when it's available.
452 switch_on_adapter_when_ready_ = true;
453 } else if (!adapter_->IsPowered()) {
454 VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
455 adapter_initially_powered_.reset(new bool(false));
456 adapter_->SetPowered(
458 base::Bind(&HIDDetectionScreenHandler::StartBTDiscoverySession,
459 weak_ptr_factory_.GetWeakPtr()),
460 base::Bind(&HIDDetectionScreenHandler::SetPoweredError,
461 weak_ptr_factory_.GetWeakPtr()));
468 void HIDDetectionScreenHandler::OnGetInputDevicesListForCheck(
469 const base::Callback<void(bool)>& on_check_done,
470 const std::vector<InputDeviceInfo>& devices) {
471 ProcessConnectedDevicesList(devices);
473 // Screen is not required if both devices are present.
474 bool all_devices_autodetected = !pointing_device_id_.empty() &&
475 !keyboard_device_id_.empty();
476 UMA_HISTOGRAM_BOOLEAN("HIDDetection.OOBEDialogShown",
477 !all_devices_autodetected);
479 on_check_done.Run(!all_devices_autodetected);
482 void HIDDetectionScreenHandler::OnGetInputDevicesList(
483 const std::vector<InputDeviceInfo>& devices) {
484 ProcessConnectedDevicesList(devices);
485 TryInitiateBTDevicesUpdate();
488 void HIDDetectionScreenHandler::ConnectBTDevice(
489 device::BluetoothDevice* device) {
490 if (!device->IsPairable() || device->IsPaired())
492 device::BluetoothDevice::DeviceType device_type = device->GetDeviceType();
494 if (device_type == device::BluetoothDevice::DEVICE_MOUSE ||
495 device_type == device::BluetoothDevice::DEVICE_TABLET) {
496 if (mouse_is_pairing_)
498 mouse_is_pairing_ = true;
499 } else if (device_type == device::BluetoothDevice::DEVICE_KEYBOARD) {
500 if (keyboard_is_pairing_)
502 keyboard_is_pairing_ = true;
503 } else if (device_type ==
504 device::BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO) {
505 if (mouse_is_pairing_ || keyboard_is_pairing_)
507 mouse_is_pairing_ = true;
508 keyboard_is_pairing_ = true;
510 device->Connect(this,
511 base::Bind(&HIDDetectionScreenHandler::BTConnected,
512 weak_ptr_factory_.GetWeakPtr(), device_type),
513 base::Bind(&HIDDetectionScreenHandler::BTConnectError,
514 weak_ptr_factory_.GetWeakPtr(),
515 device->GetAddress(), device_type));
518 void HIDDetectionScreenHandler::BTConnected(
519 device::BluetoothDevice::DeviceType device_type) {
520 if (DeviceIsPointing(device_type))
521 mouse_is_pairing_ = false;
522 if (DeviceIsKeyboard(device_type))
523 keyboard_is_pairing_ = false;
526 void HIDDetectionScreenHandler::BTConnectError(
527 const std::string& address,
528 device::BluetoothDevice::DeviceType device_type,
529 device::BluetoothDevice::ConnectErrorCode error_code) {
530 LOG(WARNING) << "BTConnectError while connecting " << address
531 << " error code = " << error_code;
532 if (DeviceIsPointing(device_type))
533 mouse_is_pairing_ = false;
534 if (DeviceIsKeyboard(device_type)) {
535 keyboard_is_pairing_ = false;
536 SendKeyboardDeviceNotification(NULL);
539 if (pointing_device_id_.empty() || keyboard_device_id_.empty())
544 void HIDDetectionScreenHandler::SendPointingDeviceNotification() {
546 if (pointing_device_id_.empty())
547 state = kSearchingState;
548 else if (pointing_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH)
549 state = kBTPairedState;
551 state = kUSBConnectedState;
552 CallJS("setPointingDeviceState", state);
555 void HIDDetectionScreenHandler::SendKeyboardDeviceNotification(
556 base::DictionaryValue* params) {
557 base::DictionaryValue state_info;
559 state_info.MergeDictionary(params);
561 base::string16 device_name;
562 if (!state_info.GetString(kDeviceNameArgName, &device_name)) {
563 device_name = l10n_util::GetStringUTF16(
564 IDS_HID_DETECTION_DEFAULT_KEYBOARD_NAME);
567 if (keyboard_device_id_.empty()) {
568 if (!state_info.HasKey("state")) {
569 state_info.SetString("state", kSearchingState);
570 } else if (state_info.HasKey(kPincodeArgName)) {
571 state_info.SetString(
573 l10n_util::GetStringFUTF16(
574 IDS_HID_DETECTION_BLUETOOTH_REMOTE_PIN_CODE_REQUEST,
577 } else if (keyboard_device_connect_type_ == InputDeviceInfo::TYPE_BLUETOOTH) {
578 state_info.SetString("state", kBTPairedState);
579 state_info.SetString(
581 l10n_util::GetStringFUTF16(
582 IDS_HID_DETECTION_PAIRED_BLUETOOTH_KEYBOARD,
583 base::UTF8ToUTF16(keyboard_device_name_)));
585 state_info.SetString("state", kUSBConnectedState);
587 CallJS("setKeyboardDeviceState", state_info);
590 } // namespace chromeos