summaryrefslogtreecommitdiff
path: root/libs/input
diff options
context:
space:
mode:
Diffstat (limited to 'libs/input')
-rw-r--r--libs/input/Android.bp22
-rw-r--r--libs/input/InputDevice.cpp5
-rw-r--r--libs/input/KeyboardClassifier.cpp89
-rw-r--r--libs/input/android/os/IInputConstants.aidl153
-rw-r--r--libs/input/input_flags.aconfig7
-rw-r--r--libs/input/rust/input.rs141
-rw-r--r--libs/input/rust/keyboard_classifier.rs345
-rw-r--r--libs/input/rust/lib.rs101
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());
+}