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