Loading libs/input/rust/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ rust_defaults { "liblogger", "liblog_rust", "inputconstants-rust", "libserde", "libserde_json", ], whole_static_libs: [ "libinput_from_rust_to_cpp", Loading libs/input/rust/data_store.rs 0 → 100644 +232 −0 Original line number Diff line number Diff line /* * 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 DataStore, used to store input related data in a persistent way. use crate::input::KeyboardType; use log::{debug, error}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{Read, Write}; use std::path::Path; use std::sync::{Arc, RwLock}; /// Data store to be used to store information that persistent across device reboots. pub struct DataStore { file_reader_writer: Box<dyn FileReaderWriter>, inner: Arc<RwLock<DataStoreInner>>, } #[derive(Default)] struct DataStoreInner { is_loaded: bool, data: Data, } #[derive(Default, Serialize, Deserialize)] struct Data { // Map storing data for keyboard classification for specific devices. #[serde(default)] keyboard_classifications: Vec<KeyboardClassification>, // NOTE: Important things to consider: // - Add any data that needs to be persisted here in this struct. // - Mark all new fields with "#[serde(default)]" for backward compatibility. // - Also, you can't modify the already added fields. // - Can add new nested fields to existing structs. e.g. Add another field to the struct // KeyboardClassification and mark it "#[serde(default)]". } #[derive(Default, Serialize, Deserialize)] struct KeyboardClassification { descriptor: String, keyboard_type: KeyboardType, is_finalized: bool, } impl DataStore { /// Creates a new instance of Data store pub fn new(file_reader_writer: Box<dyn FileReaderWriter>) -> Self { Self { file_reader_writer, inner: Default::default() } } fn load(&mut self) { if self.inner.read().unwrap().is_loaded { return; } self.load_internal(); } fn load_internal(&mut self) { let s = self.file_reader_writer.read(); let data: Data = if !s.is_empty() { let deserialize: Data = match serde_json::from_str(&s) { Ok(deserialize) => deserialize, Err(msg) => { error!("Unable to deserialize JSON data into struct: {:?} -> {:?}", msg, s); Default::default() } }; deserialize } else { Default::default() }; let mut inner = self.inner.write().unwrap(); inner.data = data; inner.is_loaded = true; } fn save(&mut self) { let string_to_save; { let inner = self.inner.read().unwrap(); string_to_save = serde_json::to_string(&inner.data).unwrap(); } self.file_reader_writer.write(string_to_save); } /// Get keyboard type of the device (as stored in the data store) pub fn get_keyboard_type(&mut self, descriptor: &String) -> Option<(KeyboardType, bool)> { self.load(); let data = &self.inner.read().unwrap().data; for keyboard_classification in data.keyboard_classifications.iter() { if keyboard_classification.descriptor == *descriptor { return Some(( keyboard_classification.keyboard_type, keyboard_classification.is_finalized, )); } } None } /// Save keyboard type of the device in the data store pub fn set_keyboard_type( &mut self, descriptor: &String, keyboard_type: KeyboardType, is_finalized: bool, ) { { let data = &mut self.inner.write().unwrap().data; data.keyboard_classifications .retain(|classification| classification.descriptor != *descriptor); data.keyboard_classifications.push(KeyboardClassification { descriptor: descriptor.to_string(), keyboard_type, is_finalized, }) } self.save(); } } pub trait FileReaderWriter { fn read(&self) -> String; fn write(&self, to_write: String); } /// Default file reader writer implementation pub struct DefaultFileReaderWriter { filepath: String, } impl DefaultFileReaderWriter { /// Creates a new instance of Default file reader writer that can read and write string to a /// particular file in the filesystem pub fn new(filepath: String) -> Self { Self { filepath } } } impl FileReaderWriter for DefaultFileReaderWriter { fn read(&self) -> String { let path = Path::new(&self.filepath); let mut fs_string = String::new(); match File::open(path) { Err(e) => error!("couldn't open {:?}: {}", path, e), Ok(mut file) => match file.read_to_string(&mut fs_string) { Err(e) => error!("Couldn't read from {:?}: {}", path, e), Ok(_) => debug!("Successfully read from file {:?}", path), }, }; fs_string } fn write(&self, to_write: String) { let path = Path::new(&self.filepath); match File::create(path) { Err(e) => error!("couldn't create {:?}: {}", path, e), Ok(mut file) => match file.write_all(to_write.as_bytes()) { Err(e) => error!("Couldn't write to {:?}: {}", path, e), Ok(_) => debug!("Successfully saved to file {:?}", path), }, }; } } #[cfg(test)] mod tests { use crate::data_store::{ test_file_reader_writer::TestFileReaderWriter, DataStore, FileReaderWriter, }; use crate::input::KeyboardType; #[test] fn test_backward_compatibility_version_1() { // This test tests JSON string that will be created by the first version of data store // This test SHOULD NOT be modified let test_reader_writer = TestFileReaderWriter::new(); test_reader_writer.write(r#"{"keyboard_classifications":[{"descriptor":"descriptor","keyboard_type":{"type":"Alphabetic"},"is_finalized":true}]}"#.to_string()); let mut data_store = DataStore::new(Box::new(test_reader_writer)); let (keyboard_type, is_finalized) = data_store.get_keyboard_type(&"descriptor".to_string()).unwrap(); assert_eq!(keyboard_type, KeyboardType::Alphabetic); assert!(is_finalized); } } #[cfg(test)] pub mod test_file_reader_writer { use crate::data_store::FileReaderWriter; use std::sync::{Arc, RwLock}; #[derive(Default)] struct TestFileReaderWriterInner { fs_string: String, } #[derive(Default, Clone)] pub struct TestFileReaderWriter(Arc<RwLock<TestFileReaderWriterInner>>); impl TestFileReaderWriter { pub fn new() -> Self { Default::default() } } impl FileReaderWriter for TestFileReaderWriter { fn read(&self) -> String { self.0.read().unwrap().fs_string.clone() } fn write(&self, fs_string: String) { self.0.write().unwrap().fs_string = fs_string; } } } libs/input/rust/input.rs +4 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ use crate::ffi::RustInputDeviceIdentifier; use bitflags::bitflags; use inputconstants::aidl::android::os::IInputConstants; use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag; use serde::{Deserialize, Serialize}; use std::fmt; /// The InputDevice ID. Loading Loading @@ -324,9 +325,11 @@ bitflags! { /// A rust enum representation of a Keyboard type. #[repr(u32)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum KeyboardType { /// KEYBOARD_TYPE_NONE #[default] None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE, /// KEYBOARD_TYPE_NON_ALPHABETIC NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, Loading libs/input/rust/keyboard_classifier.rs +95 −50 Original line number Diff line number Diff line Loading @@ -31,9 +31,8 @@ //! 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::data_store::DataStore; use crate::input::{DeviceId, InputDevice, KeyboardType}; use crate::keyboard_classification_config::CLASSIFIED_DEVICES; use crate::{DeviceClass, ModifierState}; Loading @@ -41,30 +40,28 @@ 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>, data_store: DataStore, } struct KeyboardInfo { _device: InputDevice, device: InputDevice, keyboard_type: KeyboardType, is_finalized: bool, } impl KeyboardClassifier { /// Create a new KeyboardClassifier pub fn new() -> Self { Default::default() pub fn new(data_store: DataStore) -> Self { Self { device_map: HashMap::new(), data_store } } /// 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 }, ); self.device_map .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized }); } /// Get keyboard type for a tracked keyboard in KeyboardClassifier Loading Loading @@ -107,11 +104,16 @@ impl KeyboardClassifier { if Self::is_alphabetic_key(&evdev_code) { keyboard.keyboard_type = KeyboardType::Alphabetic; keyboard.is_finalized = true; self.data_store.set_keyboard_type( &keyboard.device.identifier.descriptor, keyboard.keyboard_type, keyboard.is_finalized, ); } } } fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) { fn classify_keyboard(&mut 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) { Loading @@ -128,10 +130,17 @@ impl KeyboardClassifier { }; } // Check in data store if let Some((keyboard_type, is_finalized)) = self.data_store.get_keyboard_type(&device.identifier.descriptor) { return (keyboard_type, is_finalized); } // Check in known device list for classification for data in CLASSIFIED_DEVICES.iter() { if device.identifier.vendor == data.0 && device.identifier.product == data.1 { return (data.2, data.3); for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { if device.identifier.vendor == *vendor && device.identifier.product == *product { return (*keyboard_type, *is_finalized); } } Loading Loading @@ -177,18 +186,20 @@ impl KeyboardClassifier { #[cfg(test)] mod tests { use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore}; use crate::input::{DeviceId, InputDevice, KeyboardType}; use crate::keyboard_classification_config::CLASSIFIED_DEVICES; use crate::keyboard_classifier::KeyboardClassifier; use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier}; static DEVICE_ID: DeviceId = DeviceId(1); static SECOND_DEVICE_ID: DeviceId = DeviceId(2); static KEY_A: i32 = 30; static KEY_1: i32 = 2; #[test] fn classify_external_alphabetic_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, )); Loading @@ -198,7 +209,7 @@ mod tests { #[test] fn classify_external_non_alphabetic_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); Loading @@ -207,7 +218,7 @@ mod tests { #[test] fn classify_mouse_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Cursor Loading @@ -220,7 +231,7 @@ mod tests { #[test] fn classify_touchpad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Touchpad Loading @@ -233,7 +244,7 @@ mod tests { #[test] fn classify_stylus_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::ExternalStylus Loading @@ -246,7 +257,7 @@ mod tests { #[test] fn classify_dpad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -259,7 +270,7 @@ mod tests { #[test] fn classify_joystick_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Joystick Loading @@ -272,7 +283,7 @@ mod tests { #[test] fn classify_gamepad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Gamepad Loading @@ -285,7 +296,7 @@ mod tests { #[test] fn reclassify_keyboard_on_alphabetic_key_event() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -303,7 +314,7 @@ mod tests { #[test] fn dont_reclassify_keyboard_on_non_alphabetic_key_event() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -321,7 +332,7 @@ mod tests { #[test] fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -338,28 +349,71 @@ mod tests { #[test] fn classify_known_devices() { let mut classifier = KeyboardClassifier::new(); for device in CLASSIFIED_DEVICES.iter() { let mut classifier = create_classifier(); for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { classifier .notify_keyboard_changed(create_device_with_vendor_product_ids(device.0, device.1)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), device.2); assert_eq!(classifier.is_finalized(DEVICE_ID), device.3); .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type); assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized); } } fn create_device(classes: DeviceClass) -> InputDevice { InputDevice { device_id: DEVICE_ID, identifier: RustInputDeviceIdentifier { #[test] fn classify_previously_reclassified_devices() { let test_reader_writer = TestFileReaderWriter::new(); { let mut classifier = KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); let device = create_device( DeviceClass::Keyboard | DeviceClass::Dpad | DeviceClass::AlphabeticKey | DeviceClass::External, ); classifier.notify_keyboard_changed(device); classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); } // Re-create classifier and data store to mimic a reboot (but use the same file system // reader writer) { let mut classifier = KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); let device = InputDevice { device_id: SECOND_DEVICE_ID, identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), classes: DeviceClass::Keyboard | DeviceClass::Dpad | DeviceClass::AlphabeticKey | DeviceClass::External, }; classifier.notify_keyboard_changed(device); assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic); assert!(classifier.is_finalized(SECOND_DEVICE_ID)); } } fn create_classifier() -> KeyboardClassifier { KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new()))) } fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier { RustInputDeviceIdentifier { name: "test_device".to_string(), location: "location".to_string(), unique_id: "unique_id".to_string(), bus: 123, vendor: 234, product: 345, vendor, product, version: 567, descriptor: "descriptor".to_string(), }, } } fn create_device(classes: DeviceClass) -> InputDevice { InputDevice { device_id: DEVICE_ID, identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), classes, } } Loading @@ -367,16 +421,7 @@ mod tests { fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> 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, product, version: 567, descriptor: "descriptor".to_string(), }, identifier: create_identifier(vendor, product), classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, } } Loading libs/input/rust/lib.rs +10 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,13 @@ //! The rust component of libinput. mod data_store; mod input; mod input_verifier; mod keyboard_classification_config; mod keyboard_classifier; pub use data_store::{DataStore, DefaultFileReaderWriter}; pub use input::{ DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source, }; Loading Loading @@ -149,7 +151,14 @@ fn reset_device(verifier: &mut InputVerifier, device_id: i32) { } fn create_keyboard_classifier() -> Box<KeyboardClassifier> { Box::new(KeyboardClassifier::new()) // Future design: Make this data store singleton by passing it to C++ side and making it global // and pass by reference to components that need to store persistent data. // // Currently only used by rust keyboard classifier so keeping it here. let data_store = DataStore::new(Box::new(DefaultFileReaderWriter::new( "/data/system/inputflinger-data.json".to_string(), ))); Box::new(KeyboardClassifier::new(data_store)) } fn notify_keyboard_changed( Loading Loading
libs/input/rust/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ rust_defaults { "liblogger", "liblog_rust", "inputconstants-rust", "libserde", "libserde_json", ], whole_static_libs: [ "libinput_from_rust_to_cpp", Loading
libs/input/rust/data_store.rs 0 → 100644 +232 −0 Original line number Diff line number Diff line /* * 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 DataStore, used to store input related data in a persistent way. use crate::input::KeyboardType; use log::{debug, error}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{Read, Write}; use std::path::Path; use std::sync::{Arc, RwLock}; /// Data store to be used to store information that persistent across device reboots. pub struct DataStore { file_reader_writer: Box<dyn FileReaderWriter>, inner: Arc<RwLock<DataStoreInner>>, } #[derive(Default)] struct DataStoreInner { is_loaded: bool, data: Data, } #[derive(Default, Serialize, Deserialize)] struct Data { // Map storing data for keyboard classification for specific devices. #[serde(default)] keyboard_classifications: Vec<KeyboardClassification>, // NOTE: Important things to consider: // - Add any data that needs to be persisted here in this struct. // - Mark all new fields with "#[serde(default)]" for backward compatibility. // - Also, you can't modify the already added fields. // - Can add new nested fields to existing structs. e.g. Add another field to the struct // KeyboardClassification and mark it "#[serde(default)]". } #[derive(Default, Serialize, Deserialize)] struct KeyboardClassification { descriptor: String, keyboard_type: KeyboardType, is_finalized: bool, } impl DataStore { /// Creates a new instance of Data store pub fn new(file_reader_writer: Box<dyn FileReaderWriter>) -> Self { Self { file_reader_writer, inner: Default::default() } } fn load(&mut self) { if self.inner.read().unwrap().is_loaded { return; } self.load_internal(); } fn load_internal(&mut self) { let s = self.file_reader_writer.read(); let data: Data = if !s.is_empty() { let deserialize: Data = match serde_json::from_str(&s) { Ok(deserialize) => deserialize, Err(msg) => { error!("Unable to deserialize JSON data into struct: {:?} -> {:?}", msg, s); Default::default() } }; deserialize } else { Default::default() }; let mut inner = self.inner.write().unwrap(); inner.data = data; inner.is_loaded = true; } fn save(&mut self) { let string_to_save; { let inner = self.inner.read().unwrap(); string_to_save = serde_json::to_string(&inner.data).unwrap(); } self.file_reader_writer.write(string_to_save); } /// Get keyboard type of the device (as stored in the data store) pub fn get_keyboard_type(&mut self, descriptor: &String) -> Option<(KeyboardType, bool)> { self.load(); let data = &self.inner.read().unwrap().data; for keyboard_classification in data.keyboard_classifications.iter() { if keyboard_classification.descriptor == *descriptor { return Some(( keyboard_classification.keyboard_type, keyboard_classification.is_finalized, )); } } None } /// Save keyboard type of the device in the data store pub fn set_keyboard_type( &mut self, descriptor: &String, keyboard_type: KeyboardType, is_finalized: bool, ) { { let data = &mut self.inner.write().unwrap().data; data.keyboard_classifications .retain(|classification| classification.descriptor != *descriptor); data.keyboard_classifications.push(KeyboardClassification { descriptor: descriptor.to_string(), keyboard_type, is_finalized, }) } self.save(); } } pub trait FileReaderWriter { fn read(&self) -> String; fn write(&self, to_write: String); } /// Default file reader writer implementation pub struct DefaultFileReaderWriter { filepath: String, } impl DefaultFileReaderWriter { /// Creates a new instance of Default file reader writer that can read and write string to a /// particular file in the filesystem pub fn new(filepath: String) -> Self { Self { filepath } } } impl FileReaderWriter for DefaultFileReaderWriter { fn read(&self) -> String { let path = Path::new(&self.filepath); let mut fs_string = String::new(); match File::open(path) { Err(e) => error!("couldn't open {:?}: {}", path, e), Ok(mut file) => match file.read_to_string(&mut fs_string) { Err(e) => error!("Couldn't read from {:?}: {}", path, e), Ok(_) => debug!("Successfully read from file {:?}", path), }, }; fs_string } fn write(&self, to_write: String) { let path = Path::new(&self.filepath); match File::create(path) { Err(e) => error!("couldn't create {:?}: {}", path, e), Ok(mut file) => match file.write_all(to_write.as_bytes()) { Err(e) => error!("Couldn't write to {:?}: {}", path, e), Ok(_) => debug!("Successfully saved to file {:?}", path), }, }; } } #[cfg(test)] mod tests { use crate::data_store::{ test_file_reader_writer::TestFileReaderWriter, DataStore, FileReaderWriter, }; use crate::input::KeyboardType; #[test] fn test_backward_compatibility_version_1() { // This test tests JSON string that will be created by the first version of data store // This test SHOULD NOT be modified let test_reader_writer = TestFileReaderWriter::new(); test_reader_writer.write(r#"{"keyboard_classifications":[{"descriptor":"descriptor","keyboard_type":{"type":"Alphabetic"},"is_finalized":true}]}"#.to_string()); let mut data_store = DataStore::new(Box::new(test_reader_writer)); let (keyboard_type, is_finalized) = data_store.get_keyboard_type(&"descriptor".to_string()).unwrap(); assert_eq!(keyboard_type, KeyboardType::Alphabetic); assert!(is_finalized); } } #[cfg(test)] pub mod test_file_reader_writer { use crate::data_store::FileReaderWriter; use std::sync::{Arc, RwLock}; #[derive(Default)] struct TestFileReaderWriterInner { fs_string: String, } #[derive(Default, Clone)] pub struct TestFileReaderWriter(Arc<RwLock<TestFileReaderWriterInner>>); impl TestFileReaderWriter { pub fn new() -> Self { Default::default() } } impl FileReaderWriter for TestFileReaderWriter { fn read(&self) -> String { self.0.read().unwrap().fs_string.clone() } fn write(&self, fs_string: String) { self.0.write().unwrap().fs_string = fs_string; } } }
libs/input/rust/input.rs +4 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ use crate::ffi::RustInputDeviceIdentifier; use bitflags::bitflags; use inputconstants::aidl::android::os::IInputConstants; use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag; use serde::{Deserialize, Serialize}; use std::fmt; /// The InputDevice ID. Loading Loading @@ -324,9 +325,11 @@ bitflags! { /// A rust enum representation of a Keyboard type. #[repr(u32)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum KeyboardType { /// KEYBOARD_TYPE_NONE #[default] None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE, /// KEYBOARD_TYPE_NON_ALPHABETIC NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, Loading
libs/input/rust/keyboard_classifier.rs +95 −50 Original line number Diff line number Diff line Loading @@ -31,9 +31,8 @@ //! 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::data_store::DataStore; use crate::input::{DeviceId, InputDevice, KeyboardType}; use crate::keyboard_classification_config::CLASSIFIED_DEVICES; use crate::{DeviceClass, ModifierState}; Loading @@ -41,30 +40,28 @@ 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>, data_store: DataStore, } struct KeyboardInfo { _device: InputDevice, device: InputDevice, keyboard_type: KeyboardType, is_finalized: bool, } impl KeyboardClassifier { /// Create a new KeyboardClassifier pub fn new() -> Self { Default::default() pub fn new(data_store: DataStore) -> Self { Self { device_map: HashMap::new(), data_store } } /// 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 }, ); self.device_map .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized }); } /// Get keyboard type for a tracked keyboard in KeyboardClassifier Loading Loading @@ -107,11 +104,16 @@ impl KeyboardClassifier { if Self::is_alphabetic_key(&evdev_code) { keyboard.keyboard_type = KeyboardType::Alphabetic; keyboard.is_finalized = true; self.data_store.set_keyboard_type( &keyboard.device.identifier.descriptor, keyboard.keyboard_type, keyboard.is_finalized, ); } } } fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) { fn classify_keyboard(&mut 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) { Loading @@ -128,10 +130,17 @@ impl KeyboardClassifier { }; } // Check in data store if let Some((keyboard_type, is_finalized)) = self.data_store.get_keyboard_type(&device.identifier.descriptor) { return (keyboard_type, is_finalized); } // Check in known device list for classification for data in CLASSIFIED_DEVICES.iter() { if device.identifier.vendor == data.0 && device.identifier.product == data.1 { return (data.2, data.3); for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { if device.identifier.vendor == *vendor && device.identifier.product == *product { return (*keyboard_type, *is_finalized); } } Loading Loading @@ -177,18 +186,20 @@ impl KeyboardClassifier { #[cfg(test)] mod tests { use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore}; use crate::input::{DeviceId, InputDevice, KeyboardType}; use crate::keyboard_classification_config::CLASSIFIED_DEVICES; use crate::keyboard_classifier::KeyboardClassifier; use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier}; static DEVICE_ID: DeviceId = DeviceId(1); static SECOND_DEVICE_ID: DeviceId = DeviceId(2); static KEY_A: i32 = 30; static KEY_1: i32 = 2; #[test] fn classify_external_alphabetic_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, )); Loading @@ -198,7 +209,7 @@ mod tests { #[test] fn classify_external_non_alphabetic_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); Loading @@ -207,7 +218,7 @@ mod tests { #[test] fn classify_mouse_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Cursor Loading @@ -220,7 +231,7 @@ mod tests { #[test] fn classify_touchpad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Touchpad Loading @@ -233,7 +244,7 @@ mod tests { #[test] fn classify_stylus_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::ExternalStylus Loading @@ -246,7 +257,7 @@ mod tests { #[test] fn classify_dpad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -259,7 +270,7 @@ mod tests { #[test] fn classify_joystick_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Joystick Loading @@ -272,7 +283,7 @@ mod tests { #[test] fn classify_gamepad_pretending_as_keyboard() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Gamepad Loading @@ -285,7 +296,7 @@ mod tests { #[test] fn reclassify_keyboard_on_alphabetic_key_event() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -303,7 +314,7 @@ mod tests { #[test] fn dont_reclassify_keyboard_on_non_alphabetic_key_event() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -321,7 +332,7 @@ mod tests { #[test] fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() { let mut classifier = KeyboardClassifier::new(); let mut classifier = create_classifier(); classifier.notify_keyboard_changed(create_device( DeviceClass::Keyboard | DeviceClass::Dpad Loading @@ -338,28 +349,71 @@ mod tests { #[test] fn classify_known_devices() { let mut classifier = KeyboardClassifier::new(); for device in CLASSIFIED_DEVICES.iter() { let mut classifier = create_classifier(); for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { classifier .notify_keyboard_changed(create_device_with_vendor_product_ids(device.0, device.1)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), device.2); assert_eq!(classifier.is_finalized(DEVICE_ID), device.3); .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product)); assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type); assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized); } } fn create_device(classes: DeviceClass) -> InputDevice { InputDevice { device_id: DEVICE_ID, identifier: RustInputDeviceIdentifier { #[test] fn classify_previously_reclassified_devices() { let test_reader_writer = TestFileReaderWriter::new(); { let mut classifier = KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); let device = create_device( DeviceClass::Keyboard | DeviceClass::Dpad | DeviceClass::AlphabeticKey | DeviceClass::External, ); classifier.notify_keyboard_changed(device); classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); } // Re-create classifier and data store to mimic a reboot (but use the same file system // reader writer) { let mut classifier = KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); let device = InputDevice { device_id: SECOND_DEVICE_ID, identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), classes: DeviceClass::Keyboard | DeviceClass::Dpad | DeviceClass::AlphabeticKey | DeviceClass::External, }; classifier.notify_keyboard_changed(device); assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic); assert!(classifier.is_finalized(SECOND_DEVICE_ID)); } } fn create_classifier() -> KeyboardClassifier { KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new()))) } fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier { RustInputDeviceIdentifier { name: "test_device".to_string(), location: "location".to_string(), unique_id: "unique_id".to_string(), bus: 123, vendor: 234, product: 345, vendor, product, version: 567, descriptor: "descriptor".to_string(), }, } } fn create_device(classes: DeviceClass) -> InputDevice { InputDevice { device_id: DEVICE_ID, identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), classes, } } Loading @@ -367,16 +421,7 @@ mod tests { fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> 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, product, version: 567, descriptor: "descriptor".to_string(), }, identifier: create_identifier(vendor, product), classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, } } Loading
libs/input/rust/lib.rs +10 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,13 @@ //! The rust component of libinput. mod data_store; mod input; mod input_verifier; mod keyboard_classification_config; mod keyboard_classifier; pub use data_store::{DataStore, DefaultFileReaderWriter}; pub use input::{ DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source, }; Loading Loading @@ -149,7 +151,14 @@ fn reset_device(verifier: &mut InputVerifier, device_id: i32) { } fn create_keyboard_classifier() -> Box<KeyboardClassifier> { Box::new(KeyboardClassifier::new()) // Future design: Make this data store singleton by passing it to C++ side and making it global // and pass by reference to components that need to store persistent data. // // Currently only used by rust keyboard classifier so keeping it here. let data_store = DataStore::new(Box::new(DefaultFileReaderWriter::new( "/data/system/inputflinger-data.json".to_string(), ))); Box::new(KeyboardClassifier::new(data_store)) } fn notify_keyboard_changed( Loading