diff options
Diffstat (limited to 'libs/input')
| -rw-r--r-- | libs/input/Android.bp | 22 | ||||
| -rw-r--r-- | libs/input/InputDevice.cpp | 5 | ||||
| -rw-r--r-- | libs/input/KeyboardClassifier.cpp | 89 | ||||
| -rw-r--r-- | libs/input/android/os/IInputConstants.aidl | 153 | ||||
| -rw-r--r-- | libs/input/input_flags.aconfig | 7 | ||||
| -rw-r--r-- | libs/input/rust/input.rs | 141 | ||||
| -rw-r--r-- | libs/input/rust/keyboard_classifier.rs | 345 | ||||
| -rw-r--r-- | libs/input/rust/lib.rs | 101 |
8 files changed, 837 insertions, 26 deletions
diff --git a/libs/input/Android.bp b/libs/input/Android.bp index ed1701460a..d782f42071 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -113,6 +113,27 @@ rust_bindgen { "--allowlist-var=AINPUT_SOURCE_HDMI", "--allowlist-var=AINPUT_SOURCE_SENSOR", "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC", + "--allowlist-var=AMETA_NONE", + "--allowlist-var=AMETA_ALT_ON", + "--allowlist-var=AMETA_ALT_LEFT_ON", + "--allowlist-var=AMETA_ALT_RIGHT_ON", + "--allowlist-var=AMETA_SHIFT_ON", + "--allowlist-var=AMETA_SHIFT_LEFT_ON", + "--allowlist-var=AMETA_SHIFT_RIGHT_ON", + "--allowlist-var=AMETA_SYM_ON", + "--allowlist-var=AMETA_FUNCTION_ON", + "--allowlist-var=AMETA_CTRL_ON", + "--allowlist-var=AMETA_CTRL_LEFT_ON", + "--allowlist-var=AMETA_CTRL_RIGHT_ON", + "--allowlist-var=AMETA_META_ON", + "--allowlist-var=AMETA_META_LEFT_ON", + "--allowlist-var=AMETA_META_RIGHT_ON", + "--allowlist-var=AMETA_CAPS_LOCK_ON", + "--allowlist-var=AMETA_NUM_LOCK_ON", + "--allowlist-var=AMETA_SCROLL_LOCK_ON", ], static_libs: [ @@ -204,6 +225,7 @@ cc_library { "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", + "KeyboardClassifier.cpp", "KeyLayoutMap.cpp", "MotionPredictor.cpp", "MotionPredictorMetricsManager.cpp", diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index bc678103c2..9333ab83a6 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -273,10 +273,7 @@ void InputDeviceInfo::addLightInfo(const InputDeviceLightInfo& info) { } void InputDeviceInfo::setKeyboardType(int32_t keyboardType) { - static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); - static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // There can be multiple subdevices with different keyboard types, set it to the highest type - mKeyboardType = std::max(mKeyboardType, keyboardType); + mKeyboardType = keyboardType; } void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) { diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp new file mode 100644 index 0000000000..0c2c7be582 --- /dev/null +++ b/libs/input/KeyboardClassifier.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "KeyboardClassifier" + +#include <android-base/logging.h> +#include <com_android_input_flags.h> +#include <ftl/flags.h> +#include <input/KeyboardClassifier.h> + +#include "input_cxx_bridge.rs.h" + +namespace input_flags = com::android::input::flags; + +using android::input::RustInputDeviceIdentifier; + +namespace android { + +KeyboardClassifier::KeyboardClassifier() { + if (input_flags::enable_keyboard_classifier()) { + mRustClassifier = android::input::keyboardClassifier::create(); + } +} + +KeyboardType KeyboardClassifier::getKeyboardType(DeviceId deviceId) { + if (mRustClassifier) { + return static_cast<KeyboardType>( + android::input::keyboardClassifier::getKeyboardType(**mRustClassifier, deviceId)); + } else { + auto it = mKeyboardTypeMap.find(deviceId); + if (it == mKeyboardTypeMap.end()) { + return KeyboardType::NONE; + } + return it->second; + } +} + +// Copied from EventHub.h +const uint32_t DEVICE_CLASS_KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD; +const uint32_t DEVICE_CLASS_ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY; + +void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId, + const InputDeviceIdentifier& identifier, + uint32_t deviceClasses) { + if (mRustClassifier) { + RustInputDeviceIdentifier rustIdentifier; + rustIdentifier.name = identifier.name; + rustIdentifier.location = identifier.location; + rustIdentifier.unique_id = identifier.uniqueId; + rustIdentifier.bus = identifier.bus; + rustIdentifier.vendor = identifier.vendor; + rustIdentifier.product = identifier.product; + rustIdentifier.version = identifier.version; + rustIdentifier.descriptor = identifier.descriptor; + android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId, + rustIdentifier, deviceClasses); + } else { + bool isKeyboard = (deviceClasses & DEVICE_CLASS_KEYBOARD) != 0; + bool hasAlphabeticKey = (deviceClasses & DEVICE_CLASS_ALPHAKEY) != 0; + mKeyboardTypeMap.insert_or_assign(deviceId, + isKeyboard ? (hasAlphabeticKey + ? KeyboardType::ALPHABETIC + : KeyboardType::NON_ALPHABETIC) + : KeyboardType::NONE); + } +} + +void KeyboardClassifier::processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState) { + if (mRustClassifier && + !android::input::keyboardClassifier::isFinalized(**mRustClassifier, deviceId)) { + android::input::keyboardClassifier::processKey(**mRustClassifier, deviceId, evdevCode, + metaState); + } +} + +} // namespace android diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index 90ed2b7d06..e8746d000e 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -234,4 +234,157 @@ interface IInputConstants * time to adjust to changes in direction. */ const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9; + + + /* + * Input device class: Keyboard + * The input device is a keyboard or has buttons. + * + * @hide + */ + const int DEVICE_CLASS_KEYBOARD = 0x00000001; + + /* + * Input device class: Alphakey + * The input device is an alpha-numeric keyboard (not just a dial pad). + * + * @hide + */ + const int DEVICE_CLASS_ALPHAKEY = 0x00000002; + + /* + * Input device class: Touch + * The input device is a touchscreen or a touchpad (either single-touch or multi-touch). + * + * @hide + */ + const int DEVICE_CLASS_TOUCH = 0x00000004; + + /* + * Input device class: Cursor + * The input device is a cursor device such as a trackball or mouse. + * + * @hide + */ + const int DEVICE_CLASS_CURSOR = 0x00000008; + + /* + * Input device class: Multi-touch + * The input device is a multi-touch touchscreen or touchpad. + * + * @hide + */ + const int DEVICE_CLASS_TOUCH_MT = 0x00000010; + + /* + * Input device class: Dpad + * The input device is a directional pad (implies keyboard, has DPAD keys). + * + * @hide + */ + const int DEVICE_CLASS_DPAD = 0x00000020; + + /* + * Input device class: Gamepad + * The input device is a gamepad (implies keyboard, has BUTTON keys). + * + * @hide + */ + const int DEVICE_CLASS_GAMEPAD = 0x00000040; + + /* + * Input device class: Switch + * The input device has switches. + * + * @hide + */ + const int DEVICE_CLASS_SWITCH = 0x00000080; + + /* + * Input device class: Joystick + * The input device is a joystick (implies gamepad, has joystick absolute axes). + * + * @hide + */ + const int DEVICE_CLASS_JOYSTICK = 0x00000100; + + /* + * Input device class: Vibrator + * The input device has a vibrator (supports FF_RUMBLE). + * + * @hide + */ + const int DEVICE_CLASS_VIBRATOR = 0x00000200; + + /* + * Input device class: Mic + * The input device has a microphone. + * + * @hide + */ + const int DEVICE_CLASS_MIC = 0x00000400; + + /* + * Input device class: External Stylus + * The input device is an external stylus (has data we want to fuse with touch data). + * + * @hide + */ + const int DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800; + + /* + * Input device class: Rotary Encoder + * The input device has a rotary encoder. + * + * @hide + */ + const int DEVICE_CLASS_ROTARY_ENCODER = 0x00001000; + + /* + * Input device class: Sensor + * The input device has a sensor like accelerometer, gyro, etc. + * + * @hide + */ + const int DEVICE_CLASS_SENSOR = 0x00002000; + + /* + * Input device class: Battery + * The input device has a battery. + * + * @hide + */ + const int DEVICE_CLASS_BATTERY = 0x00004000; + + /* + * Input device class: Light + * The input device has sysfs controllable lights. + * + * @hide + */ + const int DEVICE_CLASS_LIGHT = 0x00008000; + + /* + * Input device class: Touchpad + * The input device is a touchpad, requiring an on-screen cursor. + * + * @hide + */ + const int DEVICE_CLASS_TOUCHPAD = 0x00010000; + + /* + * Input device class: Virtual + * The input device is virtual (not a real device, not part of UI configuration). + * + * @hide + */ + const int DEVICE_CLASS_VIRTUAL = 0x20000000; + + /* + * Input device class: External + * The input device is external (not built-in). + * + * @hide + */ + const int DEVICE_CLASS_EXTERNAL = 0x40000000; } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 560166c2bd..a2192cbdc4 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -150,3 +150,10 @@ flag { description: "Hide touch and pointer indicators if a secure window is present on display" bug: "325252005" } + +flag { + name: "enable_keyboard_classifier" + namespace: "input" + description: "Keyboard classifier that classifies all keyboards into alphabetic or non-alphabetic" + bug: "263559234" +} diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index d0dbd6fa16..72b421cffe 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -16,22 +16,26 @@ //! Common definitions of the Android Input Framework in rust. +use crate::ffi::RustInputDeviceIdentifier; use bitflags::bitflags; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; +use inputconstants::aidl::android::os::IInputConstants; use std::fmt; /// The InputDevice ID. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct DeviceId(pub i32); +/// The InputDevice equivalent for Rust inputflinger +#[derive(Debug)] +pub struct InputDevice { + /// InputDevice ID + pub device_id: DeviceId, + /// InputDevice unique identifier + pub identifier: RustInputDeviceIdentifier, + /// InputDevice classes (equivalent to EventHub InputDeviceClass) + pub classes: DeviceClass, +} + #[repr(u32)] pub enum SourceClass { None = input_bindgen::AINPUT_SOURCE_CLASS_NONE, @@ -192,23 +196,23 @@ bitflags! { #[derive(Debug)] pub struct MotionFlags: u32 { /// FLAG_WINDOW_IS_OBSCURED - const WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32; + const WINDOW_IS_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32; /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED - const WINDOW_IS_PARTIALLY_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32; + const WINDOW_IS_PARTIALLY_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32; /// FLAG_HOVER_EXIT_PENDING - const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32; + const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32; /// FLAG_IS_GENERATED_GESTURE - const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32; + const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32; /// FLAG_CANCELED - const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32; + const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32; /// FLAG_NO_FOCUS_CHANGE - const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; + const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; /// FLAG_IS_ACCESSIBILITY_EVENT - const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; + const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; /// FLAG_TAINTED - const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32; + const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32; /// FLAG_TARGET_ACCESSIBILITY_FOCUS - const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32; + const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32; } } @@ -220,6 +224,107 @@ impl Source { } } +bitflags! { + /// Device class of the input device. These are duplicated from Eventhub.h + /// We need to make sure the two version remain in sync when adding new classes. + #[derive(Debug, PartialEq)] + pub struct DeviceClass: u32 { + /// The input device is a keyboard or has buttons + const Keyboard = IInputConstants::DEVICE_CLASS_KEYBOARD as u32; + /// The input device is an alpha-numeric keyboard (not just a dial pad) + const AlphabeticKey = IInputConstants::DEVICE_CLASS_ALPHAKEY as u32; + /// The input device is a touchscreen or a touchpad (either single-touch or multi-touch) + const Touch = IInputConstants::DEVICE_CLASS_TOUCH as u32; + /// The input device is a cursor device such as a trackball or mouse. + const Cursor = IInputConstants::DEVICE_CLASS_CURSOR as u32; + /// The input device is a multi-touch touchscreen or touchpad. + const MultiTouch = IInputConstants::DEVICE_CLASS_TOUCH_MT as u32; + /// The input device is a directional pad (implies keyboard, has DPAD keys). + const Dpad = IInputConstants::DEVICE_CLASS_DPAD as u32; + /// The input device is a gamepad (implies keyboard, has BUTTON keys). + const Gamepad = IInputConstants::DEVICE_CLASS_GAMEPAD as u32; + /// The input device has switches. + const Switch = IInputConstants::DEVICE_CLASS_SWITCH as u32; + /// The input device is a joystick (implies gamepad, has joystick absolute axes). + const Joystick = IInputConstants::DEVICE_CLASS_JOYSTICK as u32; + /// The input device has a vibrator (supports FF_RUMBLE). + const Vibrator = IInputConstants::DEVICE_CLASS_VIBRATOR as u32; + /// The input device has a microphone. + const Mic = IInputConstants::DEVICE_CLASS_MIC as u32; + /// The input device is an external stylus (has data we want to fuse with touch data). + const ExternalStylus = IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS as u32; + /// The input device has a rotary encoder + const RotaryEncoder = IInputConstants::DEVICE_CLASS_ROTARY_ENCODER as u32; + /// The input device has a sensor like accelerometer, gyro, etc + const Sensor = IInputConstants::DEVICE_CLASS_SENSOR as u32; + /// The input device has a battery + const Battery = IInputConstants::DEVICE_CLASS_BATTERY as u32; + /// The input device has sysfs controllable lights + const Light = IInputConstants::DEVICE_CLASS_LIGHT as u32; + /// The input device is a touchpad, requiring an on-screen cursor. + const Touchpad = IInputConstants::DEVICE_CLASS_TOUCHPAD as u32; + /// The input device is virtual (not a real device, not part of UI configuration). + const Virtual = IInputConstants::DEVICE_CLASS_VIRTUAL as u32; + /// The input device is external (not built-in). + const External = IInputConstants::DEVICE_CLASS_EXTERNAL as u32; + } +} + +bitflags! { + /// Modifier state flags + #[derive(Debug, PartialEq)] + pub struct ModifierState: u32 { + /// No meta keys are pressed + const None = input_bindgen::AMETA_NONE; + /// This mask is used to check whether one of the ALT meta keys is pressed + const AltOn = input_bindgen::AMETA_ALT_ON; + /// This mask is used to check whether the left ALT meta key is pressed + const AltLeftOn = input_bindgen::AMETA_ALT_LEFT_ON; + /// This mask is used to check whether the right ALT meta key is pressed + const AltRightOn = input_bindgen::AMETA_ALT_RIGHT_ON; + /// This mask is used to check whether one of the SHIFT meta keys is pressed + const ShiftOn = input_bindgen::AMETA_SHIFT_ON; + /// This mask is used to check whether the left SHIFT meta key is pressed + const ShiftLeftOn = input_bindgen::AMETA_SHIFT_LEFT_ON; + /// This mask is used to check whether the right SHIFT meta key is pressed + const ShiftRightOn = input_bindgen::AMETA_SHIFT_RIGHT_ON; + /// This mask is used to check whether the SYM meta key is pressed + const SymOn = input_bindgen::AMETA_SYM_ON; + /// This mask is used to check whether the FUNCTION meta key is pressed + const FunctionOn = input_bindgen::AMETA_FUNCTION_ON; + /// This mask is used to check whether one of the CTRL meta keys is pressed + const CtrlOn = input_bindgen::AMETA_CTRL_ON; + /// This mask is used to check whether the left CTRL meta key is pressed + const CtrlLeftOn = input_bindgen::AMETA_CTRL_LEFT_ON; + /// This mask is used to check whether the right CTRL meta key is pressed + const CtrlRightOn = input_bindgen::AMETA_CTRL_RIGHT_ON; + /// This mask is used to check whether one of the META meta keys is pressed + const MetaOn = input_bindgen::AMETA_META_ON; + /// This mask is used to check whether the left META meta key is pressed + const MetaLeftOn = input_bindgen::AMETA_META_LEFT_ON; + /// This mask is used to check whether the right META meta key is pressed + const MetaRightOn = input_bindgen::AMETA_META_RIGHT_ON; + /// This mask is used to check whether the CAPS LOCK meta key is on + const CapsLockOn = input_bindgen::AMETA_CAPS_LOCK_ON; + /// This mask is used to check whether the NUM LOCK meta key is on + const NumLockOn = input_bindgen::AMETA_NUM_LOCK_ON; + /// This mask is used to check whether the SCROLL LOCK meta key is on + const ScrollLockOn = input_bindgen::AMETA_SCROLL_LOCK_ON; + } +} + +/// A rust enum representation of a Keyboard type. +#[repr(u32)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum KeyboardType { + /// KEYBOARD_TYPE_NONE + None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE, + /// KEYBOARD_TYPE_NON_ALPHABETIC + NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, + /// KEYBOARD_TYPE_ALPHABETIC + Alphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_ALPHABETIC, +} + #[cfg(test)] mod tests { use crate::input::SourceClass; diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs new file mode 100644 index 0000000000..1063fac664 --- /dev/null +++ b/libs/input/rust/keyboard_classifier.rs @@ -0,0 +1,345 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Contains the KeyboardClassifier, that tries to identify whether an Input device is an +//! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device +//! in order to verify/change the inferred keyboard type. +//! +//! Initial classification: +//! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad, +//! Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic +//! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic +//! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic +//! +//! On process keys: +//! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to +//! KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true) +//! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event +//! across multiple device connections in a time period, then change type to +//! KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic +//! (i.e. verified = false). +//! +//! TODO(b/263559234): Data store implementation to store information about past classification + +use crate::input::{DeviceId, InputDevice, KeyboardType}; +use crate::{DeviceClass, ModifierState}; +use std::collections::HashMap; + +/// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic +/// keyboard or non-alphabetic keyboard +#[derive(Default)] +pub struct KeyboardClassifier { + device_map: HashMap<DeviceId, KeyboardInfo>, +} + +struct KeyboardInfo { + _device: InputDevice, + keyboard_type: KeyboardType, + is_finalized: bool, +} + +impl KeyboardClassifier { + /// Create a new KeyboardClassifier + pub fn new() -> Self { + Default::default() + } + + /// Adds keyboard to KeyboardClassifier + pub fn notify_keyboard_changed(&mut self, device: InputDevice) { + let (keyboard_type, is_finalized) = self.classify_keyboard(&device); + self.device_map.insert( + device.device_id, + KeyboardInfo { _device: device, keyboard_type, is_finalized }, + ); + } + + /// Get keyboard type for a tracked keyboard in KeyboardClassifier + pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType { + return if let Some(keyboard) = self.device_map.get(&device_id) { + keyboard.keyboard_type + } else { + KeyboardType::None + }; + } + + /// Tells if keyboard type classification is finalized. Once finalized the classification can't + /// change until device is reconnected again. + /// + /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or + /// allowlist that are explicitly categorized and won't change with future key events + pub fn is_finalized(&self, device_id: DeviceId) -> bool { + return if let Some(keyboard) = self.device_map.get(&device_id) { + keyboard.is_finalized + } else { + false + }; + } + + /// Process a key event and change keyboard type if required. + /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic + /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic + pub fn process_key( + &mut self, + device_id: DeviceId, + evdev_code: i32, + modifier_state: ModifierState, + ) { + if let Some(keyboard) = self.device_map.get_mut(&device_id) { + // Ignore all key events with modifier state since they can be macro shortcuts used by + // some non-keyboard peripherals like TV remotes, game controllers, etc. + if modifier_state.bits() != 0 { + return; + } + if Self::is_alphabetic_key(&evdev_code) { + keyboard.keyboard_type = KeyboardType::Alphabetic; + keyboard.is_finalized = true; + } + } + } + + fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) { + // This should never happen but having keyboard device class is necessary to be classified + // as any type of keyboard. + if !device.classes.contains(DeviceClass::Keyboard) { + return (KeyboardType::None, true); + } + // Normal classification for internal and virtual keyboards + if !device.classes.contains(DeviceClass::External) + || device.classes.contains(DeviceClass::Virtual) + { + return if device.classes.contains(DeviceClass::AlphabeticKey) { + (KeyboardType::Alphabetic, true) + } else { + (KeyboardType::NonAlphabetic, true) + }; + } + // Any composite device with multiple device classes should be categorized as non-alphabetic + // keyboard initially + if device.classes.contains(DeviceClass::Touch) + || device.classes.contains(DeviceClass::Cursor) + || device.classes.contains(DeviceClass::MultiTouch) + || device.classes.contains(DeviceClass::ExternalStylus) + || device.classes.contains(DeviceClass::Touchpad) + || device.classes.contains(DeviceClass::Dpad) + || device.classes.contains(DeviceClass::Gamepad) + || device.classes.contains(DeviceClass::Switch) + || device.classes.contains(DeviceClass::Joystick) + || device.classes.contains(DeviceClass::RotaryEncoder) + { + // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the + // kernel, we no longer need to process key events to verify. + return ( + KeyboardType::NonAlphabetic, + !device.classes.contains(DeviceClass::AlphabeticKey), + ); + } + // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard + if device.classes.contains(DeviceClass::AlphabeticKey) { + (KeyboardType::Alphabetic, true) + } else { + // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the + // kernel, we no longer need to process key events to verify. + (KeyboardType::NonAlphabetic, true) + } + } + + fn is_alphabetic_key(evdev_code: &i32) -> bool { + // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ]) + (16..=27).contains(evdev_code) + // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `) + || (30..=41).contains(evdev_code) + // Keyboard alphabetic row 3 (\ Z X C V B N M , . /) + || (43..=53).contains(evdev_code) + } +} + +#[cfg(test)] +mod tests { + use crate::input::{DeviceId, InputDevice, KeyboardType}; + use crate::keyboard_classifier::KeyboardClassifier; + use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier}; + + static DEVICE_ID: DeviceId = DeviceId(1); + static KEY_A: i32 = 30; + static KEY_1: i32 = 2; + + #[test] + fn classify_external_alphabetic_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_external_non_alphabetic_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier + .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External)); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_mouse_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Cursor + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_touchpad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Touchpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_stylus_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::ExternalStylus + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_dpad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_joystick_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Joystick + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_gamepad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Gamepad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn reclassify_keyboard_on_alphabetic_key_event() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + // on alphabetic key event + classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn dont_reclassify_keyboard_on_non_alphabetic_key_event() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + // on number key event + classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + fn create_device(classes: DeviceClass) -> InputDevice { + InputDevice { + device_id: DEVICE_ID, + identifier: RustInputDeviceIdentifier { + name: "test_device".to_string(), + location: "location".to_string(), + unique_id: "unique_id".to_string(), + bus: 123, + vendor: 234, + product: 345, + version: 567, + descriptor: "descriptor".to_string(), + }, + classes, + } + } +} diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs index fb3f520c01..5010475334 100644 --- a/libs/input/rust/lib.rs +++ b/libs/input/rust/lib.rs @@ -18,9 +18,13 @@ mod input; mod input_verifier; +mod keyboard_classifier; -pub use input::{DeviceId, MotionAction, MotionFlags, Source}; +pub use input::{ + DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source, +}; pub use input_verifier::InputVerifier; +pub use keyboard_classifier::KeyboardClassifier; #[cxx::bridge(namespace = "android::input")] #[allow(unsafe_op_in_unsafe_fn)] @@ -47,7 +51,8 @@ mod ffi { /// } /// ``` type InputVerifier; - fn create(name: String) -> Box<InputVerifier>; + #[cxx_name = create] + fn create_input_verifier(name: String) -> Box<InputVerifier>; fn process_movement( verifier: &mut InputVerifier, device_id: i32, @@ -59,15 +64,53 @@ mod ffi { fn reset_device(verifier: &mut InputVerifier, device_id: i32); } + #[namespace = "android::input::keyboardClassifier"] + extern "Rust" { + /// Used to classify a keyboard into alphabetic and non-alphabetic + type KeyboardClassifier; + #[cxx_name = create] + fn create_keyboard_classifier() -> Box<KeyboardClassifier>; + #[cxx_name = notifyKeyboardChanged] + fn notify_keyboard_changed( + classifier: &mut KeyboardClassifier, + device_id: i32, + identifier: RustInputDeviceIdentifier, + device_classes: u32, + ); + #[cxx_name = getKeyboardType] + fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32; + #[cxx_name = isFinalized] + fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool; + #[cxx_name = processKey] + fn process_key( + classifier: &mut KeyboardClassifier, + device_id: i32, + evdev_code: i32, + modifier_state: u32, + ); + } + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct RustPointerProperties { pub id: i32, } + + #[derive(Debug)] + pub struct RustInputDeviceIdentifier { + pub name: String, + pub location: String, + pub unique_id: String, + pub bus: u16, + pub vendor: u16, + pub product: u16, + pub version: u16, + pub descriptor: String, + } } -use crate::ffi::RustPointerProperties; +use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties}; -fn create(name: String) -> Box<InputVerifier> { +fn create_input_verifier(name: String) -> Box<InputVerifier> { Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"))) } @@ -103,3 +146,53 @@ fn process_movement( fn reset_device(verifier: &mut InputVerifier, device_id: i32) { verifier.reset_device(DeviceId(device_id)); } + +fn create_keyboard_classifier() -> Box<KeyboardClassifier> { + Box::new(KeyboardClassifier::new()) +} + +fn notify_keyboard_changed( + classifier: &mut KeyboardClassifier, + device_id: i32, + identifier: RustInputDeviceIdentifier, + device_classes: u32, +) { + let classes = DeviceClass::from_bits(device_classes); + if classes.is_none() { + panic!( + "The conversion of device class 0x{:08x} failed, please check if some device classes + have not been added to DeviceClass.", + device_classes + ); + } + classifier.notify_keyboard_changed(InputDevice { + device_id: DeviceId(device_id), + identifier, + classes: classes.unwrap(), + }); +} + +fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32 { + classifier.get_keyboard_type(DeviceId(device_id)) as u32 +} + +fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool { + classifier.is_finalized(DeviceId(device_id)) +} + +fn process_key( + classifier: &mut KeyboardClassifier, + device_id: i32, + evdev_code: i32, + meta_state: u32, +) { + let modifier_state = ModifierState::from_bits(meta_state); + if modifier_state.is_none() { + panic!( + "The conversion of meta state 0x{:08x} failed, please check if some meta state + have not been added to ModifierState.", + meta_state + ); + } + classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap()); +} |