blob: e84b6ac7a14197aec34fe766001969d14175f09c [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/input_device_settings/input_device_notifier.h"
#include <algorithm>
#include <functional>
#include <memory>
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/test/ash_test_base.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
namespace ash {
using DeviceId = InputDeviceSettingsController::DeviceId;
namespace {
const char kUserEmail[] = "example@email.com";
const char kBluetoothDeviceName[] = "Bluetooth Device";
const char kBluetoothDevicePublicAddress[] = "01:23:45:67:89:AB";
const ui::KeyboardDevice kSampleKeyboardInternal = {
5, ui::INPUT_DEVICE_INTERNAL, "kSampleKeyboardInternal"};
const ui::KeyboardDevice kSampleKeyboardBluetooth = {
10, ui::INPUT_DEVICE_BLUETOOTH, "kSampleKeyboardBluetooth"};
const ui::KeyboardDevice kSampleKeyboardUsb = {15, ui::INPUT_DEVICE_USB,
"kSampleKeyboardUsb"};
const ui::KeyboardDevice kSampleKeyboardUsb2 = {16, ui::INPUT_DEVICE_USB,
"kSampleKeyboardUsb2"};
const ui::KeyboardDevice kLogitechMXKeysKeyboard = {
20,
ui::INPUT_DEVICE_BLUETOOTH,
"Logitech MX Keys",
/*phys=*/"",
/*sys_path=*/base::FilePath(),
/*vendor=*/0x046d,
/*product=*/0xb35b,
/*version=*/0x0};
const ui::InputDevice kSampleMouseUsb = {20, ui::INPUT_DEVICE_USB,
"kSampleMouseUsb"};
const ui::InputDevice kSampleMouseBluetooth = {25, ui::INPUT_DEVICE_BLUETOOTH,
"kSampleMouseBluetooth"};
const ui::InputDevice kSampleMouseInternal = {30, ui::INPUT_DEVICE_INTERNAL,
"kSampleMouseInternal"};
template <typename Comp = std::ranges::less>
void SortDevices(std::vector<ui::KeyboardDevice>& devices, Comp comp = {}) {
std::ranges::sort(devices, comp, [](const ui::KeyboardDevice& keyboard) {
return keyboard.id;
});
}
} // namespace
struct InputDeviceNotifierParamaterizedTestData {
InputDeviceNotifierParamaterizedTestData() = default;
InputDeviceNotifierParamaterizedTestData(
std::vector<ui::KeyboardDevice> initial_devices,
std::vector<ui::KeyboardDevice> updated_devices,
std::vector<ui::KeyboardDevice> expected_devices_to_add,
std::vector<ui::KeyboardDevice> expected_devices_to_remove)
: initial_devices(initial_devices),
updated_devices(updated_devices),
expected_devices_to_add(expected_devices_to_add),
expected_devices_to_remove(expected_devices_to_remove) {
SortAllLists();
}
void SortAllLists() {
SortDevices(initial_devices);
SortDevices(updated_devices);
SortDevices(expected_devices_to_add);
SortDevices(expected_devices_to_remove);
}
std::vector<ui::KeyboardDevice> initial_devices;
std::vector<ui::KeyboardDevice> updated_devices;
std::vector<ui::KeyboardDevice> expected_devices_to_add;
std::vector<ui::KeyboardDevice> expected_devices_to_remove;
};
class InputDeviceStateNotifierTest : public AshTestBase {
public:
InputDeviceStateNotifierTest() = default;
InputDeviceStateNotifierTest(const InputDeviceStateNotifierTest&) = delete;
InputDeviceStateNotifierTest& operator=(const InputDeviceStateNotifierTest&) =
delete;
~InputDeviceStateNotifierTest() override = default;
// testing::Test:
void SetUp() override {
bluetooth_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(bluetooth_adapter_);
ON_CALL(*bluetooth_adapter_, IsPowered)
.WillByDefault(testing::Return(true));
ON_CALL(*bluetooth_adapter_, IsPresent)
.WillByDefault(testing::Return(true));
AshTestBase::SetUp();
notifier_ = std::make_unique<
InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>(
&keyboards_,
base::BindRepeating(&InputDeviceStateNotifierTest::SaveNotifierResults,
base::Unretained(this)));
}
void TearDown() override {
devices_to_add_.clear();
device_ids_to_remove_.clear();
notifier_.reset();
AshTestBase::TearDown();
}
void SaveNotifierResults(std::vector<ui::KeyboardDevice> devices_to_add,
std::vector<DeviceId> device_ids_to_remove) {
devices_to_add_ = std::move(devices_to_add);
device_ids_to_remove_ = std::move(device_ids_to_remove);
}
protected:
std::unique_ptr<InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>
notifier_;
base::flat_map<DeviceId, mojom::KeyboardPtr> keyboards_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
bluetooth_adapter_;
std::vector<ui::KeyboardDevice> devices_to_add_;
std::vector<DeviceId> device_ids_to_remove_;
};
TEST_F(InputDeviceStateNotifierTest, ImpostersRemoved) {
ui::KeyboardDevice imposter_keyboard = kSampleKeyboardUsb;
imposter_keyboard.vendor_id = 0x1234;
imposter_keyboard.product_id = 0x5678;
imposter_keyboard.suspected_keyboard_imposter = true;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[0].id);
imposter_keyboard.suspected_keyboard_imposter = false;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
}
TEST_F(InputDeviceStateNotifierTest, ImpostersRemembered) {
ui::KeyboardDevice imposter_keyboard = kSampleKeyboardUsb;
imposter_keyboard.vendor_id = 0x1234;
imposter_keyboard.product_id = 0x5678;
imposter_keyboard.suspected_keyboard_imposter = true;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[0].id);
// Remove imposter flag and make sure the notifier includes the old
// "imposter".
imposter_keyboard.suspected_keyboard_imposter = false;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
// Remove the imposter and then add it back and ensure it was remembered as
// being a previously valid device.
ui::DeviceDataManagerTestApi().SetKeyboardDevices({kSampleKeyboardUsb2});
imposter_keyboard.suspected_keyboard_imposter = true;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
}
// Since Logitech MX Keys pretends to be a mouse, it should always be shown as a
// keyboard even if it is a suspected imposter. We know better due to our
// block/allowlist.
TEST_F(InputDeviceStateNotifierTest,
KeyboardWhichImpersonatesMouseAlwaysShown) {
ui::KeyboardDevice imposter_keyboard = kLogitechMXKeysKeyboard;
imposter_keyboard.suspected_keyboard_imposter = true;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{kSampleKeyboardUsb, imposter_keyboard});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb.id, devices_to_add_[0].id);
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[1].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[1].id);
imposter_keyboard.suspected_keyboard_imposter = false;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{kSampleKeyboardUsb, imposter_keyboard});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb.id, devices_to_add_[0].id);
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[1].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[1].id);
}
TEST_F(InputDeviceStateNotifierTest, BluetoothKeyboardTest) {
uint32_t test_vendor_id = 0x1111;
uint32_t test_product_id = 0x1112;
std::unique_ptr<device::MockBluetoothDevice> mock_device =
std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
bluetooth_adapter_.get(), /*bluetooth_class=*/0, kBluetoothDeviceName,
kBluetoothDevicePublicAddress,
/*initially_paired=*/true, /*connected=*/true);
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::KEYBOARD));
ON_CALL(*mock_device, GetVendorID)
.WillByDefault(testing::Return(test_vendor_id));
ON_CALL(*mock_device, GetProductID)
.WillByDefault(testing::Return(test_product_id));
std::vector<raw_ptr<const device::BluetoothDevice, VectorExperimental>>
devices;
devices.push_back(mock_device.get());
ON_CALL(*bluetooth_adapter_, GetDevices)
.WillByDefault(testing::Return(devices));
ui::KeyboardDevice bluetooth_keyboard = kSampleKeyboardBluetooth;
bluetooth_keyboard.product_id = test_product_id;
bluetooth_keyboard.vendor_id = test_vendor_id;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{bluetooth_keyboard, kSampleKeyboardInternal});
ASSERT_EQ(2u, devices_to_add_.size());
// Keyboard + Mouse combo devices are included in the keyboard list.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(
testing::Return(device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO));
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// Mice should not be included with keyboards.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::MOUSE));
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
ASSERT_EQ(1u, devices_to_add_.size());
// Gamepads should not be included with keyboards.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::GAMEPAD));
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
ASSERT_EQ(1u, devices_to_add_.size());
// Once it is a keyboard again, it should be added back to the list.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::KEYBOARD));
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// If there are no bluetooth devices according to the bluetooth_adapter_, the
// notifier should just trust the device data manager.
devices.clear();
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// Clear `bluetooth_adapter_` mock so that it would not return dangling
// pointers after return.
ASSERT_TRUE(testing::Mock::VerifyAndClear(bluetooth_adapter_.get()));
}
class InputDeviceStateLoginScreenNotifierTest : public NoSessionAshTestBase {
public:
InputDeviceStateLoginScreenNotifierTest() = default;
InputDeviceStateLoginScreenNotifierTest(
const InputDeviceStateLoginScreenNotifierTest&) = delete;
InputDeviceStateLoginScreenNotifierTest& operator=(
const InputDeviceStateLoginScreenNotifierTest&) = delete;
~InputDeviceStateLoginScreenNotifierTest() override = default;
// testing::Test:
void SetUp() override {
bluetooth_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(bluetooth_adapter_);
ON_CALL(*bluetooth_adapter_, IsPowered)
.WillByDefault(testing::Return(true));
ON_CALL(*bluetooth_adapter_, IsPresent)
.WillByDefault(testing::Return(true));
NoSessionAshTestBase::SetUp();
notifier_ = std::make_unique<
InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>(
&keyboards_,
base::BindRepeating(
&InputDeviceStateLoginScreenNotifierTest::SaveNotifierResults,
base::Unretained(this)));
}
void TearDown() override {
devices_to_add_.clear();
device_ids_to_remove_.clear();
notifier_.reset();
NoSessionAshTestBase::TearDown();
}
void SaveNotifierResults(std::vector<ui::KeyboardDevice> devices_to_add,
std::vector<DeviceId> device_ids_to_remove) {
devices_to_add_ = std::move(devices_to_add);
device_ids_to_remove_ = std::move(device_ids_to_remove);
}
protected:
std::unique_ptr<InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>
notifier_;
base::flat_map<DeviceId, mojom::KeyboardPtr> keyboards_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
bluetooth_adapter_;
std::vector<ui::KeyboardDevice> devices_to_add_;
std::vector<DeviceId> device_ids_to_remove_;
};
TEST_F(InputDeviceStateLoginScreenNotifierTest, ImpostersIgnoredOnLoginScreen) {
ui::KeyboardDevice imposter_keyboard = kSampleKeyboardUsb;
imposter_keyboard.vendor_id = 0x1234;
imposter_keyboard.product_id = 0x5678;
imposter_keyboard.suspected_keyboard_imposter = true;
// On the login screen, assume the imposter flag is invalid and ignore it.
// Therefore, imposter keyboards should be considered "connected".
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
SimulateUserLogin({kUserEmail});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[0].id);
}
TEST_F(InputDeviceStateLoginScreenNotifierTest,
ImpostersRememberedFromLoginScreen) {
ui::KeyboardDevice imposter_keyboard = kSampleKeyboardUsb;
imposter_keyboard.vendor_id = 0x1234;
imposter_keyboard.product_id = 0x5678;
imposter_keyboard.suspected_keyboard_imposter = true;
// On the login screen, assume the imposter flag is invalid and ignore it.
// Therefore, imposter keyboards should be considered "connected".
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
imposter_keyboard.suspected_keyboard_imposter = false;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
// Remove the imposter keyboard and then login.
ui::DeviceDataManagerTestApi().SetKeyboardDevices({kSampleKeyboardUsb2});
SimulateUserLogin({kUserEmail});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[0].id);
imposter_keyboard.suspected_keyboard_imposter = true;
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
{imposter_keyboard, kSampleKeyboardUsb2});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_keyboard.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_keyboard.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleKeyboardUsb2.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleKeyboardUsb2.id, devices_to_add_[1].id);
}
class InputDeviceNotifierParamaterizedTest
: public InputDeviceStateNotifierTest,
public testing::WithParamInterface<
InputDeviceNotifierParamaterizedTestData> {
public:
InputDeviceNotifierParamaterizedTest() = default;
InputDeviceNotifierParamaterizedTest(
const InputDeviceNotifierParamaterizedTest&) = delete;
InputDeviceNotifierParamaterizedTest& operator=(
const InputDeviceNotifierParamaterizedTest&) = delete;
~InputDeviceNotifierParamaterizedTest() override = default;
// testing::Test:
void SetUp() override {
InputDeviceStateNotifierTest::SetUp();
test_data_ = GetParam();
Initialize();
notifier_ = std::make_unique<
InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>>(
&keyboards_,
base::BindRepeating(
&InputDeviceNotifierParamaterizedTest::SaveNotifierResults,
base::Unretained(this)));
}
void TearDown() override {
devices_to_add_.clear();
device_ids_to_remove_.clear();
notifier_.reset();
AshTestBase::TearDown();
}
void SaveNotifierResults(std::vector<ui::KeyboardDevice> devices_to_add,
std::vector<DeviceId> device_ids_to_remove) {
devices_to_add_ = std::move(devices_to_add);
device_ids_to_remove_ = std::move(device_ids_to_remove);
}
void Initialize() {
for (const auto& device : test_data_.initial_devices) {
auto keyboard = mojom::Keyboard::New();
keyboard->id = device.id;
keyboard->name = device.name;
keyboards_[device.id] = std::move(keyboard);
}
ui::DeviceDataManagerTestApi().SetKeyboardDevices(
test_data_.updated_devices);
}
void CheckNotifierResults() {
ASSERT_EQ(test_data_.expected_devices_to_add.size(),
devices_to_add_.size());
for (size_t i = 0; i < test_data_.expected_devices_to_add.size(); i++) {
EXPECT_EQ(test_data_.expected_devices_to_add[i].id,
devices_to_add_[i].id);
EXPECT_EQ(test_data_.expected_devices_to_add[i].name,
devices_to_add_[i].name);
}
ASSERT_EQ(test_data_.expected_devices_to_remove.size(),
device_ids_to_remove_.size());
for (size_t i = 0; i < test_data_.expected_devices_to_remove.size(); i++) {
EXPECT_EQ((DeviceId)test_data_.expected_devices_to_remove[i].id,
device_ids_to_remove_[i]);
}
}
protected:
InputDeviceNotifierParamaterizedTestData test_data_;
base::flat_map<DeviceId, mojom::KeyboardPtr> keyboards_;
std::vector<ui::KeyboardDevice> devices_to_add_;
std::vector<DeviceId> device_ids_to_remove_;
};
INSTANTIATE_TEST_SUITE_P(
// Empty to simplify gtest output
,
InputDeviceNotifierParamaterizedTest,
testing::Values(
// Empty input and update results in empty output.
InputDeviceNotifierParamaterizedTestData({}, {}, {}, {}),
// Empty at start and add 3 devices.
InputDeviceNotifierParamaterizedTestData(
{},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{}),
// 3 devices at start and all are removed.
InputDeviceNotifierParamaterizedTestData(
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{},
{},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb}),
// 3 devices at start and none are removed.
InputDeviceNotifierParamaterizedTestData(
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{},
{}),
// 2 devices at start and middle id device is added.
InputDeviceNotifierParamaterizedTestData(
{kSampleKeyboardInternal, kSampleKeyboardUsb},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{kSampleKeyboardBluetooth},
{}),
// 1 device at start which is removed when another is added.
InputDeviceNotifierParamaterizedTestData({kSampleKeyboardInternal},
{kSampleKeyboardBluetooth},
{kSampleKeyboardBluetooth},
{kSampleKeyboardInternal}),
// 2 devices at start and a low id device is added.
InputDeviceNotifierParamaterizedTestData(
{kSampleKeyboardBluetooth, kSampleKeyboardUsb},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{kSampleKeyboardInternal},
{}),
// 2 devices at start and a high id device is added.
InputDeviceNotifierParamaterizedTestData(
{kSampleKeyboardInternal, kSampleKeyboardBluetooth},
{kSampleKeyboardInternal, kSampleKeyboardBluetooth,
kSampleKeyboardUsb},
{kSampleKeyboardUsb},
{})));
TEST_P(InputDeviceNotifierParamaterizedTest,
OnInputDeviceConfigurationChanged) {
ui::DeviceDataManagerTestApi()
.NotifyObserversKeyboardDeviceConfigurationChanged();
CheckNotifierResults();
}
TEST_P(InputDeviceNotifierParamaterizedTest, OnDeviceListsComplete) {
ui::DeviceDataManagerTestApi().OnDeviceListsComplete();
CheckNotifierResults();
}
class InputDeviceMouseNotifierTest : public AshTestBase {
public:
InputDeviceMouseNotifierTest() = default;
InputDeviceMouseNotifierTest(const InputDeviceMouseNotifierTest&) = delete;
InputDeviceMouseNotifierTest& operator=(const InputDeviceMouseNotifierTest&) =
delete;
~InputDeviceMouseNotifierTest() override = default;
// testing::Test:
void SetUp() override {
bluetooth_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(bluetooth_adapter_);
ON_CALL(*bluetooth_adapter_, IsPowered)
.WillByDefault(testing::Return(true));
ON_CALL(*bluetooth_adapter_, IsPresent)
.WillByDefault(testing::Return(true));
AshTestBase::SetUp();
notifier_ =
std::make_unique<InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>>(
&mice_, base::BindRepeating(
&InputDeviceMouseNotifierTest::SaveNotifierResults,
base::Unretained(this)));
}
void TearDown() override {
devices_to_add_.clear();
device_ids_to_remove_.clear();
mice_.clear();
notifier_.reset();
AshTestBase::TearDown();
}
void SaveNotifierResults(std::vector<ui::InputDevice> devices_to_add,
std::vector<DeviceId> device_ids_to_remove) {
devices_to_add_ = std::move(devices_to_add);
device_ids_to_remove_ = std::move(device_ids_to_remove);
}
protected:
InputDeviceNotifierParamaterizedTestData test_data_;
std::unique_ptr<InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>>
notifier_;
base::flat_map<DeviceId, mojom::MousePtr> mice_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
bluetooth_adapter_;
std::vector<ui::InputDevice> devices_to_add_;
std::vector<DeviceId> device_ids_to_remove_;
};
// When an internal mouse in the list received from DeviceDataManager, filter it
// out as this is likely not a real device.
TEST_F(InputDeviceMouseNotifierTest, InternalMiceFilteredOut) {
ui::DeviceDataManagerTestApi().SetMouseDevices(
{kSampleMouseUsb, kSampleMouseBluetooth, kSampleMouseInternal});
EXPECT_TRUE(device_ids_to_remove_.empty());
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(kSampleMouseUsb.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleMouseUsb.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[1].id);
}
TEST_F(InputDeviceMouseNotifierTest, BluetoothMouseTest) {
uint32_t test_vendor_id = 0x1111;
uint32_t test_product_id = 0x1112;
std::unique_ptr<device::MockBluetoothDevice> mock_device =
std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
bluetooth_adapter_.get(), /*bluetooth_class=*/0, kBluetoothDeviceName,
kBluetoothDevicePublicAddress,
/*initially_paired=*/true, /*connected=*/true);
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::MOUSE));
ON_CALL(*mock_device, GetVendorID)
.WillByDefault(testing::Return(test_vendor_id));
ON_CALL(*mock_device, GetProductID)
.WillByDefault(testing::Return(test_product_id));
std::vector<raw_ptr<const device::BluetoothDevice, VectorExperimental>>
devices;
devices.push_back(mock_device.get());
ON_CALL(*bluetooth_adapter_, GetDevices)
.WillByDefault(testing::Return(devices));
ui::InputDevice bluetooth_mouse = kSampleMouseBluetooth;
bluetooth_mouse.product_id = test_product_id;
bluetooth_mouse.vendor_id = test_vendor_id;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{bluetooth_mouse, kSampleMouseUsb});
ASSERT_EQ(2u, devices_to_add_.size());
// Keyboard + Mouse combo devices are included in the mouse list.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(
testing::Return(device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO));
ui::DeviceDataManagerTestApi()
.NotifyObserversMouseDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// Keyboards should not be included with mice.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::KEYBOARD));
ui::DeviceDataManagerTestApi()
.NotifyObserversMouseDeviceConfigurationChanged();
ASSERT_EQ(1u, devices_to_add_.size());
// Gamepads should not be included with mice.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::GAMEPAD));
ui::DeviceDataManagerTestApi()
.NotifyObserversMouseDeviceConfigurationChanged();
ASSERT_EQ(1u, devices_to_add_.size());
// Once it is a mouse again, it should be added back to the list.
ON_CALL(*mock_device, GetDeviceType)
.WillByDefault(testing::Return(device::BluetoothDeviceType::MOUSE));
ui::DeviceDataManagerTestApi()
.NotifyObserversMouseDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// If there are no bluetooth devices according to the bluetooth_adapter_, the
// notifier should just trust the device data manager.
devices.clear();
ui::DeviceDataManagerTestApi()
.NotifyObserversMouseDeviceConfigurationChanged();
ASSERT_EQ(2u, devices_to_add_.size());
// Clear `bluetooth_adapter_` mock so that it would not return dangling
// pointers after return.
ASSERT_TRUE(testing::Mock::VerifyAndClear(bluetooth_adapter_.get()));
}
TEST_F(InputDeviceMouseNotifierTest, ImpostersRemoved) {
ui::InputDevice imposter_mouse = kSampleMouseUsb;
imposter_mouse.vendor_id = 0x1234;
imposter_mouse.product_id = 0x5678;
imposter_mouse.suspected_mouse_imposter = true;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{imposter_mouse, kSampleMouseBluetooth});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[0].id);
imposter_mouse.suspected_mouse_imposter = false;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{imposter_mouse, kSampleMouseBluetooth});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_mouse.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_mouse.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[1].id);
}
TEST_F(InputDeviceMouseNotifierTest, ImpostersRemembered) {
ui::InputDevice imposter_mouse = kSampleMouseUsb;
imposter_mouse.vendor_id = 0x1234;
imposter_mouse.product_id = 0x5678;
imposter_mouse.suspected_mouse_imposter = true;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{imposter_mouse, kSampleMouseBluetooth});
ASSERT_EQ(1u, devices_to_add_.size());
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[0].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[0].id);
// Remove imposter flag and make sure the notifier includes the old
// "imposter".
imposter_mouse.suspected_mouse_imposter = false;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{imposter_mouse, kSampleMouseBluetooth});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_mouse.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_mouse.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[1].id);
// Remove the imposter and then add it back and ensure it was remembered as
// being a previously valid device.
ui::DeviceDataManagerTestApi().SetMouseDevices({kSampleMouseBluetooth});
imposter_mouse.suspected_mouse_imposter = true;
ui::DeviceDataManagerTestApi().SetMouseDevices(
{imposter_mouse, kSampleMouseBluetooth});
ASSERT_EQ(2u, devices_to_add_.size());
EXPECT_EQ(imposter_mouse.name, devices_to_add_[0].name);
EXPECT_EQ(imposter_mouse.id, devices_to_add_[0].id);
EXPECT_EQ(kSampleMouseBluetooth.name, devices_to_add_[1].name);
EXPECT_EQ(kSampleMouseBluetooth.id, devices_to_add_[1].id);
}
} // namespace ash