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(&params);
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(&params);
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(&params);
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