| // 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 |