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