blob: b5cb944ca7770de861847795d44044ddd8c36df6 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/screens/hid_detection_screen.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/login/screens/hid_detection_view.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
// Possible ui-states for device-blocks.
const char kSearchingState[] = "searching";
const char kUSBState[] = "usb";
const char kConnectedState[] = "connected";
const char kBTPairedState[] = "paired";
const char kBTPairingState[] = "pairing";
// Standard length of pincode for pairing BT keyboards.
const int kPincodeLength = 6;
bool DeviceIsPointing(device::BluetoothDeviceType device_type) {
return device_type == device::BluetoothDeviceType::MOUSE ||
device_type == device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO ||
device_type == device::BluetoothDeviceType::TABLET;
}
bool DeviceIsPointing(const device::mojom::InputDeviceInfoPtr& info) {
return info->is_mouse || info->is_touchpad || info->is_touchscreen ||
info->is_tablet;
}
bool DeviceIsKeyboard(device::BluetoothDeviceType device_type) {
return device_type == device::BluetoothDeviceType::KEYBOARD ||
device_type == device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO;
}
} // namespace
namespace chromeos {
HIDDetectionScreen::HIDDetectionScreen(
HIDDetectionView* view,
const base::RepeatingClosure& exit_callback)
: BaseScreen(OobeScreen::SCREEN_OOBE_HID_DETECTION),
view_(view),
exit_callback_(exit_callback),
binding_(this),
weak_ptr_factory_(this) {
if (view_)
view_->Bind(this);
device::BluetoothAdapterFactory::GetAdapter(base::BindOnce(
&HIDDetectionScreen::InitializeAdapter, weak_ptr_factory_.GetWeakPtr()));
ConnectToInputDeviceManager();
}
HIDDetectionScreen::~HIDDetectionScreen() {
adapter_initially_powered_.reset();
if (view_)
view_->Unbind();
if (discovery_session_.get())
discovery_session_->Stop(base::DoNothing(), base::DoNothing());
if (adapter_.get())
adapter_->RemoveObserver(this);
}
void HIDDetectionScreen::OnContinueButtonClicked() {
ContinueScenarioType scenario_type;
if (!pointing_device_id_.empty() && !keyboard_device_id_.empty())
scenario_type = All_DEVICES_DETECTED;
else if (pointing_device_id_.empty())
scenario_type = KEYBOARD_DEVICE_ONLY_DETECTED;
else
scenario_type = POINTING_DEVICE_ONLY_DETECTED;
UMA_HISTOGRAM_ENUMERATION("HIDDetection.OOBEDevicesDetectedOnContinuePressed",
scenario_type, CONTINUE_SCENARIO_TYPE_SIZE);
// Switch off BT adapter if it was off before the screen and no BT device
// connected.
const bool adapter_is_powered =
adapter_.get() && adapter_->IsPresent() && adapter_->IsPowered();
const bool need_switching_off =
adapter_initially_powered_ && !(*adapter_initially_powered_);
if (adapter_is_powered && need_switching_off)
PowerOff();
exit_callback_.Run();
}
void HIDDetectionScreen::OnViewDestroyed(HIDDetectionView* view) {
if (view_ == view)
view_ = nullptr;
}
void HIDDetectionScreen::CheckIsScreenRequired(
const base::Callback<void(bool)>& on_check_done) {
DCHECK(input_device_manager_);
input_device_manager_->GetDevices(
base::BindOnce(&HIDDetectionScreen::OnGetInputDevicesListForCheck,
weak_ptr_factory_.GetWeakPtr(), on_check_done));
}
void HIDDetectionScreen::Show() {
if (showing_)
return;
showing_ = true;
if (view_)
view_->SetNumKeysEnteredExpected(false);
SendPointingDeviceNotification();
SendKeyboardDeviceNotification();
if (!devices_enumerated_)
GetInputDevicesList();
else
UpdateDevices();
if (view_)
view_->Show();
}
void HIDDetectionScreen::Hide() {
if (!showing_)
return;
showing_ = false;
if (discovery_session_.get()) {
discovery_session_->Stop(base::DoNothing(), base::DoNothing());
}
if (view_)
view_->Hide();
}
void HIDDetectionScreen::RequestPinCode(device::BluetoothDevice* device) {
VLOG(1) << "RequestPinCode id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
device->CancelPairing();
}
void HIDDetectionScreen::RequestPasskey(device::BluetoothDevice* device) {
VLOG(1) << "RequestPassKey id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
device->CancelPairing();
}
void HIDDetectionScreen::DisplayPinCode(device::BluetoothDevice* device,
const std::string& pincode) {
VLOG(1) << "DisplayPinCode id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
if (view_)
view_->SetKeyboardPinCode(pincode);
SetKeyboardDeviceName(base::UTF16ToUTF8(device->GetNameForDisplay()));
SendKeyboardDeviceNotification();
}
void HIDDetectionScreen::DisplayPasskey(device::BluetoothDevice* device,
uint32_t passkey) {
VLOG(1) << "DisplayPassKey id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
std::string pincode = base::NumberToString(passkey);
pincode = std::string(kPincodeLength - pincode.length(), '0').append(pincode);
// No differences in UI for passkey and pincode authentication calls.
DisplayPinCode(device, pincode);
}
void HIDDetectionScreen::KeysEntered(device::BluetoothDevice* device,
uint32_t entered) {
VLOG(1) << "Number of keys entered " << entered;
if (view_) {
view_->SetNumKeysEnteredExpected(true);
view_->SetNumKeysEnteredPinCode(entered);
}
SendKeyboardDeviceNotification();
}
void HIDDetectionScreen::ConfirmPasskey(device::BluetoothDevice* device,
uint32_t passkey) {
VLOG(1) << "Confirm Passkey";
device->CancelPairing();
}
void HIDDetectionScreen::AuthorizePairing(device::BluetoothDevice* device) {
// There is never any circumstance where this will be called, since the
// HID detection screen will only be used for outgoing pairing
// requests, but play it safe.
VLOG(1) << "Authorize pairing";
device->ConfirmPairing();
}
void HIDDetectionScreen::AdapterPresentChanged(
device::BluetoothAdapter* adapter,
bool present) {
if (present && switch_on_adapter_when_ready_) {
VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
adapter_initially_powered_.reset(new bool(adapter_->IsPowered()));
adapter_->SetPowered(
true,
base::Bind(&HIDDetectionScreen::StartBTDiscoverySession,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&HIDDetectionScreen::SetPoweredError,
weak_ptr_factory_.GetWeakPtr()));
}
}
void HIDDetectionScreen::TryPairingAsPointingDevice(
device::BluetoothDevice* device) {
if (pointing_device_id_.empty() &&
DeviceIsPointing(device->GetDeviceType()) && device->IsPairable() &&
!(device->IsConnected() && device->IsPaired()) && !mouse_is_pairing_) {
ConnectBTDevice(device);
}
}
void HIDDetectionScreen::TryPairingAsKeyboardDevice(
device::BluetoothDevice* device) {
if (keyboard_device_id_.empty() &&
DeviceIsKeyboard(device->GetDeviceType()) && device->IsPairable() &&
!(device->IsConnected() && device->IsPaired()) && !keyboard_is_pairing_) {
ConnectBTDevice(device);
}
}
void HIDDetectionScreen::ConnectBTDevice(device::BluetoothDevice* device) {
bool device_busy =
(device->IsConnected() && device->IsPaired()) || device->IsConnecting();
if (!device->IsPairable() || device_busy)
return;
device::BluetoothDeviceType device_type = device->GetDeviceType();
if (device_type == device::BluetoothDeviceType::MOUSE ||
device_type == device::BluetoothDeviceType::TABLET) {
if (mouse_is_pairing_)
return;
mouse_is_pairing_ = true;
} else if (device_type == device::BluetoothDeviceType::KEYBOARD) {
if (keyboard_is_pairing_)
return;
keyboard_is_pairing_ = true;
} else if (device_type == device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO) {
if (mouse_is_pairing_ && keyboard_is_pairing_)
return;
mouse_is_pairing_ = true;
keyboard_is_pairing_ = true;
}
device->Connect(this,
base::Bind(&HIDDetectionScreen::BTConnected,
weak_ptr_factory_.GetWeakPtr(), device_type),
base::Bind(&HIDDetectionScreen::BTConnectError,
weak_ptr_factory_.GetWeakPtr(),
device->GetAddress(), device_type));
}
void HIDDetectionScreen::BTConnected(device::BluetoothDeviceType device_type) {
if (DeviceIsPointing(device_type))
mouse_is_pairing_ = false;
if (DeviceIsKeyboard(device_type)) {
keyboard_is_pairing_ = false;
if (view_) {
view_->SetNumKeysEnteredExpected(false);
view_->SetKeyboardPinCode("");
}
SendKeyboardDeviceNotification();
}
}
void HIDDetectionScreen::BTConnectError(
const std::string& address,
device::BluetoothDeviceType device_type,
device::BluetoothDevice::ConnectErrorCode error_code) {
LOG(WARNING) << "BTConnectError while connecting " << address
<< " error code = " << error_code;
if (DeviceIsPointing(device_type))
mouse_is_pairing_ = false;
if (DeviceIsKeyboard(device_type)) {
keyboard_is_pairing_ = false;
if (view_) {
view_->SetNumKeysEnteredExpected(false);
view_->SetKeyboardPinCode("");
}
SendKeyboardDeviceNotification();
}
if (pointing_device_id_.empty() || keyboard_device_id_.empty())
UpdateDevices();
}
void HIDDetectionScreen::SendPointingDeviceNotification() {
std::string state;
if (pointing_device_id_.empty())
state = kSearchingState;
else if (pointing_device_connect_type_ ==
device::mojom::InputDeviceType::TYPE_BLUETOOTH)
state = kBTPairedState;
else if (pointing_device_connect_type_ ==
device::mojom::InputDeviceType::TYPE_USB)
state = kUSBState;
else
state = kConnectedState;
if (view_) {
view_->SetMouseState(state);
view_->SetContinueButtonEnabled(
!(pointing_device_id_.empty() && keyboard_device_id_.empty()));
}
}
void HIDDetectionScreen::SendKeyboardDeviceNotification() {
if (!view_)
return;
view_->SetKeyboardDeviceLabel("");
if (keyboard_device_id_.empty()) {
if (keyboard_is_pairing_) {
view_->SetKeyboardState(kBTPairingState);
view_->SetKeyboardDeviceLabel(l10n_util::GetStringFUTF8(
IDS_HID_DETECTION_BLUETOOTH_REMOTE_PIN_CODE_REQUEST,
base::UTF8ToUTF16(keyboard_device_name_)));
} else {
view_->SetKeyboardState(kSearchingState);
}
} else {
if (keyboard_device_connect_type_ ==
device::mojom::InputDeviceType::TYPE_BLUETOOTH) {
view_->SetKeyboardState(kBTPairedState);
view_->SetKeyboardDeviceLabel(
l10n_util::GetStringFUTF8(IDS_HID_DETECTION_PAIRED_BLUETOOTH_KEYBOARD,
base::UTF8ToUTF16(keyboard_device_name_)));
} else {
view_->SetKeyboardState(kUSBState);
}
}
view_->SetKeyboardDeviceName(keyboard_device_name_);
view_->SetContinueButtonEnabled(
!(pointing_device_id_.empty() && keyboard_device_id_.empty()));
}
void HIDDetectionScreen::SetKeyboardDeviceName(const std::string& name) {
keyboard_device_name_ =
keyboard_device_id_.empty() || !name.empty()
? name
: l10n_util::GetStringUTF8(IDS_HID_DETECTION_DEFAULT_KEYBOARD_NAME);
}
void HIDDetectionScreen::DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
VLOG(1) << "BT input device added id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
TryPairingAsPointingDevice(device);
TryPairingAsKeyboardDevice(device);
}
void HIDDetectionScreen::DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
VLOG(1) << "BT device changed id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
TryPairingAsPointingDevice(device);
TryPairingAsKeyboardDevice(device);
}
void HIDDetectionScreen::DeviceRemoved(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
VLOG(1) << "BT device removed id = " << device->GetDeviceID()
<< " name = " << device->GetNameForDisplay();
}
void HIDDetectionScreen::InputDeviceAdded(InputDeviceInfoPtr info) {
VLOG(1) << "Input device added id = " << info->id << " name = " << info->name;
const InputDeviceInfoPtr& info_ref = devices_[info->id] = std::move(info);
if (!showing_)
return;
// TODO(merkulova): deal with all available device types, e.g. joystick.
if (!keyboard_device_id_.empty() && !pointing_device_id_.empty())
return;
if (pointing_device_id_.empty() && DeviceIsPointing(info_ref)) {
pointing_device_id_ = info_ref->id;
if (view_)
view_->SetMouseDeviceName(info_ref->name);
pointing_device_connect_type_ = info_ref->type;
SendPointingDeviceNotification();
}
if (keyboard_device_id_.empty() && info_ref->is_keyboard) {
keyboard_device_id_ = info_ref->id;
keyboard_device_connect_type_ = info_ref->type;
SetKeyboardDeviceName(info_ref->name);
SendKeyboardDeviceNotification();
}
}
void HIDDetectionScreen::InputDeviceRemoved(const std::string& id) {
devices_.erase(id);
if (!showing_)
return;
if (id == keyboard_device_id_) {
keyboard_device_id_.clear();
keyboard_device_connect_type_ =
device::mojom::InputDeviceType::TYPE_UNKNOWN;
SendKeyboardDeviceNotification();
UpdateDevices();
}
if (id == pointing_device_id_) {
pointing_device_id_.clear();
pointing_device_connect_type_ =
device::mojom::InputDeviceType::TYPE_UNKNOWN;
SendPointingDeviceNotification();
UpdateDevices();
}
}
void HIDDetectionScreen::InitializeAdapter(
scoped_refptr<device::BluetoothAdapter> adapter) {
adapter_ = adapter;
CHECK(adapter_.get());
adapter_->AddObserver(this);
}
void HIDDetectionScreen::StartBTDiscoverySession() {
adapter_->StartDiscoverySession(
base::Bind(&HIDDetectionScreen::OnStartDiscoverySession,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&HIDDetectionScreen::FindDevicesError,
weak_ptr_factory_.GetWeakPtr()));
}
void HIDDetectionScreen::ProcessConnectedDevicesList() {
for (const auto& map_entry : devices_) {
if (!pointing_device_id_.empty() && !keyboard_device_id_.empty())
return;
if (pointing_device_id_.empty() && DeviceIsPointing(map_entry.second)) {
pointing_device_id_ = map_entry.second->id;
if (view_)
view_->SetMouseDeviceName(map_entry.second->name);
pointing_device_connect_type_ = map_entry.second->type;
SendPointingDeviceNotification();
}
if (keyboard_device_id_.empty() && (map_entry.second->is_keyboard)) {
keyboard_device_id_ = map_entry.second->id;
SetKeyboardDeviceName(map_entry.second->name);
keyboard_device_connect_type_ = map_entry.second->type;
SendKeyboardDeviceNotification();
}
}
}
void HIDDetectionScreen::TryInitiateBTDevicesUpdate() {
if ((pointing_device_id_.empty() || keyboard_device_id_.empty()) &&
adapter_.get()) {
if (!adapter_->IsPresent()) {
// Switch on BT adapter later when it's available.
switch_on_adapter_when_ready_ = true;
} else if (!adapter_->IsPowered()) {
VLOG(1) << "Switching on BT adapter on HID OOBE screen.";
adapter_initially_powered_.reset(new bool(false));
adapter_->SetPowered(
true,
base::Bind(&HIDDetectionScreen::StartBTDiscoverySession,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&HIDDetectionScreen::SetPoweredError,
weak_ptr_factory_.GetWeakPtr()));
} else {
UpdateBTDevices();
}
}
}
void HIDDetectionScreen::ConnectToInputDeviceManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(content::ServiceManagerConnection::GetForProcess());
service_manager::Connector* connector =
content::ServiceManagerConnection::GetForProcess()->GetConnector();
connector->BindInterface(device::mojom::kServiceName,
mojo::MakeRequest(&input_device_manager_));
}
void HIDDetectionScreen::OnGetInputDevicesListForCheck(
const base::Callback<void(bool)>& on_check_done,
std::vector<InputDeviceInfoPtr> devices) {
std::string pointing_device_id;
std::string keyboard_device_id;
for (const auto& device : devices) {
if (pointing_device_id.empty() && DeviceIsPointing(device))
pointing_device_id = device->id;
if (keyboard_device_id.empty() && device->is_keyboard)
keyboard_device_id = device->id;
if (!pointing_device_id.empty() && !keyboard_device_id.empty())
break;
}
// Screen is not required if both devices are present.
const bool all_devices_autodetected =
!pointing_device_id.empty() && !keyboard_device_id.empty();
UMA_HISTOGRAM_BOOLEAN("HIDDetection.OOBEDialogShown",
!all_devices_autodetected);
on_check_done.Run(!all_devices_autodetected);
}
void HIDDetectionScreen::OnGetInputDevicesList(
std::vector<InputDeviceInfoPtr> devices) {
devices_enumerated_ = true;
for (auto& device : devices) {
devices_[device->id] = std::move(device);
}
UpdateDevices();
}
void HIDDetectionScreen::GetInputDevicesList() {
device::mojom::InputDeviceManagerClientAssociatedPtrInfo client;
binding_.Bind(mojo::MakeRequest(&client));
DCHECK(input_device_manager_);
input_device_manager_->GetDevicesAndSetClient(
std::move(client),
base::BindOnce(&HIDDetectionScreen::OnGetInputDevicesList,
weak_ptr_factory_.GetWeakPtr()));
}
void HIDDetectionScreen::UpdateDevices() {
ProcessConnectedDevicesList();
TryInitiateBTDevicesUpdate();
}
void HIDDetectionScreen::UpdateBTDevices() {
if (!adapter_.get() || !adapter_->IsPresent() || !adapter_->IsPowered())
return;
// If no connected devices found as pointing device and keyboard, we try to
// connect some type-suitable active bluetooth device.
std::vector<device::BluetoothDevice*> bt_devices = adapter_->GetDevices();
for (std::vector<device::BluetoothDevice*>::const_iterator it =
bt_devices.begin();
it != bt_devices.end() &&
(keyboard_device_id_.empty() || pointing_device_id_.empty());
++it) {
TryPairingAsPointingDevice(*it);
TryPairingAsKeyboardDevice(*it);
}
}
void HIDDetectionScreen::OnStartDiscoverySession(
std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
VLOG(1) << "BT Discovery session started";
discovery_session_ = std::move(discovery_session);
UpdateDevices();
}
void HIDDetectionScreen::PowerOff() {
bool use_bluetooth = false;
for (const auto& map_entry : devices_) {
if (map_entry.second->type ==
device::mojom::InputDeviceType::TYPE_BLUETOOTH) {
use_bluetooth = true;
break;
}
}
if (!use_bluetooth) {
VLOG(1) << "Switching off BT adapter after HID OOBE screen as unused.";
adapter_->SetPowered(false, base::DoNothing(),
base::Bind(&HIDDetectionScreen::SetPoweredOffError,
weak_ptr_factory_.GetWeakPtr()));
}
}
void HIDDetectionScreen::SetPoweredError() {
LOG(ERROR) << "Failed to power BT adapter";
}
void HIDDetectionScreen::SetPoweredOffError() {
LOG(ERROR) << "Failed to power off BT adapter";
}
void HIDDetectionScreen::FindDevicesError() {
VLOG(1) << "Failed to start Bluetooth discovery.";
}
scoped_refptr<device::BluetoothAdapter>
HIDDetectionScreen::GetAdapterForTesting() {
return adapter_;
}
void HIDDetectionScreen::SetAdapterInitialPoweredForTesting(bool powered) {
adapter_initially_powered_.reset(new bool(powered));
}
} // namespace chromeos