#include "ash/system/input_device_settings/input_device_notifier.h"
#include <algorithm>
#include <functional>
#include <vector>
#include "ash/bluetooth_devices_observer.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_metadata.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/input_device_settings/input_device_settings_utils.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/values.h"
#include "components/prefs/pref_service.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/bluetooth_device.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/devices/touchpad_device.h"
namespace ash {
namespace {
using DeviceId = InputDeviceSettingsController::DeviceId;
bool AreOnLoginScreen() {
auto status = Shell::Get()->session_controller()->login_status();
return status == LoginStatus::NOT_LOGGED_IN;
}
template <class DeviceMojomPtr>
bool IsDeviceASuspectedImposter(BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
return false;
}
bool IsKeyboardAKnownImposterFalsePositive(const ui::InputDevice& device) {
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted()) {
return false;
}
PrefService* prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!prefs) {
return false;
}
const auto& imposters =
prefs->GetList(prefs::kKeyboardDeviceImpostersListPref);
const std::string device_key = BuildDeviceKey(device);
return base::Contains(imposters, device_key);
}
bool IsMouseAKnownImposterFalsePositive(const ui::InputDevice& device) {
if (!features::IsMouseImposterCheckEnabled()) {
return false;
}
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted()) {
return false;
}
PrefService* prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!prefs) {
return false;
}
const auto& imposters = prefs->GetList(prefs::kMouseDeviceImpostersListPref);
const std::string device_key = BuildDeviceKey(device);
return base::Contains(imposters, device_key);
}
void SaveKeyboardsToImposterPref(
base::flat_set<std::string>& imposter_false_positives_to_add) {
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted()) {
return;
}
PrefService* prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!prefs) {
return;
}
auto updated_imposters =
prefs->GetList(prefs::kKeyboardDeviceImpostersListPref).Clone();
for (const auto& device_key : imposter_false_positives_to_add) {
if (base::Contains(updated_imposters, device_key)) {
continue;
}
updated_imposters.Append(device_key);
}
prefs->SetList(prefs::kKeyboardDeviceImpostersListPref,
std::move(updated_imposters));
imposter_false_positives_to_add.clear();
}
void SaveMiceToImposterPref(
base::flat_set<std::string>& imposter_false_positives_to_add) {
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted()) {
return;
}
PrefService* prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!prefs) {
return;
}
auto updated_imposters =
prefs->GetList(prefs::kMouseDeviceImpostersListPref).Clone();
for (const auto& device_key : imposter_false_positives_to_add) {
if (base::Contains(updated_imposters, device_key)) {
continue;
}
updated_imposters.Append(device_key);
}
prefs->SetList(prefs::kMouseDeviceImpostersListPref,
std::move(updated_imposters));
imposter_false_positives_to_add.clear();
}
template <>
bool IsDeviceASuspectedImposter<mojom::KeyboardPtr>(
BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
if (AreOnLoginScreen()) {
return false;
}
const auto device_type = GetDeviceType(device);
switch (device_type) {
case DeviceType::kKeyboard:
case DeviceType::kKeyboardMouseCombo:
return false;
case DeviceType::kMouse:
return true;
case DeviceType::kUnknown:
break;
}
if (IsKeyboardAKnownImposterFalsePositive(device)) {
return false;
}
if (device.type == ui::INPUT_DEVICE_BLUETOOTH) {
const auto* bluetooth_device =
bluetooth_observer->GetConnectedBluetoothDevice(device);
if (!bluetooth_device) {
return false;
}
if (bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD ||
bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO) {
return false;
}
return true;
}
return device.suspected_keyboard_imposter;
}
template <>
bool IsDeviceASuspectedImposter<mojom::MousePtr>(
BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
if (AreOnLoginScreen()) {
return false;
}
const auto device_type = GetDeviceType(device);
switch (device_type) {
case DeviceType::kKeyboard:
return true;
case DeviceType::kKeyboardMouseCombo:
case DeviceType::kMouse:
return false;
case DeviceType::kUnknown:
break;
}
if (IsMouseAKnownImposterFalsePositive(device)) {
return false;
}
if (device.type == ui::INPUT_DEVICE_BLUETOOTH) {
const auto* bluetooth_device =
bluetooth_observer->GetConnectedBluetoothDevice(device);
if (!bluetooth_device) {
return false;
}
if (bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::MOUSE ||
bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO) {
return false;
}
return true;
}
if (!features::IsMouseImposterCheckEnabled()) {
return false;
}
return device.suspected_mouse_imposter;
}
template <typename T>
DeviceId ExtractDeviceIdFromDeviceMapPair(
const std::pair<DeviceId, T>& id_t_pair) {
return id_t_pair.first;
}
template <class DeviceMojomPtr, typename InputDeviceType>
void GetAddedAndRemovedDevices(
BluetoothDevicesObserver* bluetooth_observer,
std::vector<InputDeviceType> updated_device_list,
const base::flat_map<DeviceId, DeviceMojomPtr>& connected_devices,
std::vector<InputDeviceType>* devices_to_add,
std::vector<DeviceId>* devices_to_remove) {
devices_to_add->clear();
devices_to_remove->clear();
std::erase_if(updated_device_list, [&](const ui::InputDevice& device) {
return IsDeviceASuspectedImposter<DeviceMojomPtr>(bluetooth_observer,
device);
});
std::vector<int> updated_device_ids;
updated_device_ids.reserve(updated_device_list.size());
std::ranges::transform(updated_device_list,
std::back_inserter(updated_device_ids),
&ui::InputDevice::id);
std::ranges::sort(updated_device_ids);
std::vector<DeviceId> connected_devices_ids;
connected_devices_ids.reserve(connected_devices.size());
std::ranges::transform(connected_devices,
std::back_inserter(connected_devices_ids),
ExtractDeviceIdFromDeviceMapPair<DeviceMojomPtr>);
DCHECK(std::ranges::is_sorted(connected_devices_ids));
std::set<DeviceId> device_ids_to_add;
std::ranges::set_difference(
updated_device_ids, connected_devices_ids,
std::inserter(device_ids_to_add, device_ids_to_add.begin()));
std::ranges::copy_if(updated_device_list, std::back_inserter(*devices_to_add),
[&](const auto& device) {
return device_ids_to_add.contains(device.id);
});
std::ranges::set_difference(connected_devices_ids, updated_device_ids,
std::back_inserter(*devices_to_remove));
}
}
template <typename MojomDevicePtr, typename InputDeviceType>
InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::InputDeviceNotifier(
base::flat_map<DeviceId, MojomDevicePtr>* connected_devices,
InputDeviceListsUpdatedCallback callback)
: connected_devices_(connected_devices),
device_lists_updated_callback_(callback) {
DCHECK(connected_devices_);
ui::DeviceDataManager::GetInstance()->AddObserver(this);
Shell::Get()->session_controller()->AddObserver(this);
bluetooth_devices_observer_ =
std::make_unique<BluetoothDevicesObserver>(base::BindRepeating(
&InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnBluetoothAdapterOrDeviceChanged,
base::Unretained(this)));
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::~InputDeviceNotifier() {
ui::DeviceDataManager::GetInstance()->RemoveObserver(this);
Shell::Get()->session_controller()->RemoveObserver(this);
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::RefreshDevices() {
std::vector<InputDeviceType> devices_to_add;
std::vector<DeviceId> device_ids_to_remove;
std::vector<InputDeviceType> updated_device_list = GetUpdatedDeviceList();
HandleImposterPref(updated_device_list);
GetAddedAndRemovedDevices(bluetooth_devices_observer_.get(),
updated_device_list, *connected_devices_,
&devices_to_add, &device_ids_to_remove);
device_lists_updated_callback_.Run(std::move(devices_to_add),
std::move(device_ids_to_remove));
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::HandleImposterPref(
const std::vector<InputDeviceType>& updated_device_list) {
return;
}
template <>
void InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>::HandleImposterPref(
const std::vector<ui::InputDevice>& updated_device_list) {
if (!features::IsMouseImposterCheckEnabled()) {
return;
}
base::flat_set<DeviceId> updated_imposter_devices;
for (const ui::InputDevice& device : updated_device_list) {
if (device.suspected_mouse_imposter) {
updated_imposter_devices.insert(device.id);
continue;
}
if (mouse_imposter_devices_.contains(device.id)) {
mouse_imposter_false_positives_to_add_.insert(BuildDeviceKey(device));
}
}
mouse_imposter_devices_ = std::move(updated_imposter_devices);
SaveMiceToImposterPref(mouse_imposter_false_positives_to_add_);
}
template <>
void InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>::
HandleImposterPref(
const std::vector<ui::KeyboardDevice>& updated_device_list) {
base::flat_set<DeviceId> updated_imposter_devices;
for (const ui::KeyboardDevice& device : updated_device_list) {
if (device.suspected_keyboard_imposter) {
updated_imposter_devices.insert(device.id);
continue;
}
if (keyboard_imposter_devices_.contains(device.id)) {
keyboard_imposter_false_positives_to_add_.insert(BuildDeviceKey(device));
}
}
keyboard_imposter_devices_ = std::move(updated_imposter_devices);
SaveKeyboardsToImposterPref(keyboard_imposter_false_positives_to_add_);
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr,
InputDeviceType>::OnDeviceListsComplete() {
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnInputDeviceConfigurationChanged(uint8_t input_device_type) {
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::OnLoginStatusChanged(
LoginStatus login_status) {
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnBluetoothAdapterOrDeviceChanged(device::BluetoothDevice* device) {
}
template <>
std::vector<ui::KeyboardDevice>
InputDeviceNotifier<mojom::KeyboardPtr,
ui::KeyboardDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetKeyboardDevices();
}
template <>
std::vector<ui::TouchpadDevice>
InputDeviceNotifier<mojom::TouchpadPtr,
ui::TouchpadDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetTouchpadDevices();
}
template <>
std::vector<ui::InputDevice>
InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>::GetUpdatedDeviceList() {
auto mice = ui::DeviceDataManager::GetInstance()->GetMouseDevices();
std::erase_if(mice, [](const auto& mouse) {
return mouse.type == ui::INPUT_DEVICE_INTERNAL;
});
return mice;
}
template <>
std::vector<ui::InputDevice>
InputDeviceNotifier<mojom::PointingStickPtr,
ui::InputDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetPointingStickDevices();
}
template <>
std::vector<ui::InputDevice>
InputDeviceNotifier<mojom::GraphicsTabletPtr,
ui::InputDevice>::GetUpdatedDeviceList() {
DCHECK(ash::features::IsPeripheralCustomizationEnabled());
return ui::DeviceDataManager::GetInstance()->GetGraphicsTabletDevices();
}
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::TouchpadPtr, ui::TouchpadDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::PointingStickPtr, ui::InputDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::GraphicsTabletPtr, ui::InputDevice>;
}