diff options
| -rw-r--r-- | libs/input/rust/Android.bp | 2 | ||||
| -rw-r--r-- | libs/input/rust/data_store.rs | 232 | ||||
| -rw-r--r-- | libs/input/rust/input.rs | 5 | ||||
| -rw-r--r-- | libs/input/rust/keyboard_classifier.rs | 145 | ||||
| -rw-r--r-- | libs/input/rust/lib.rs | 11 |
5 files changed, 343 insertions, 52 deletions
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp index 018d199ce2..63853f77fa 100644 --- a/libs/input/rust/Android.bp +++ b/libs/input/rust/Android.bp @@ -24,6 +24,8 @@ rust_defaults { "liblogger", "liblog_rust", "inputconstants-rust", + "libserde", + "libserde_json", ], whole_static_libs: [ "libinput_from_rust_to_cpp", diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs new file mode 100644 index 0000000000..6bdcefda36 --- /dev/null +++ b/libs/input/rust/data_store.rs @@ -0,0 +1,232 @@ +/* + * 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; + } + } +} diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index c46b7bb85c..90f509d97f 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -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. @@ -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, diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs index 8721ef7687..3c789b41e2 100644 --- a/libs/input/rust/keyboard_classifier.rs +++ b/libs/input/rust/keyboard_classifier.rs @@ -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}; @@ -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 @@ -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) { @@ -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); } } @@ -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, )); @@ -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); @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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); + } + } + + #[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, + product, + version: 567, + descriptor: "descriptor".to_string(), } } 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(), - }, + identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), classes, } } @@ -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, } } diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs index af8f889cb5..008f675485 100644 --- a/libs/input/rust/lib.rs +++ b/libs/input/rust/lib.rs @@ -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, }; @@ -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( |