Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 099fb592 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Create basic data store to store keyboard classification

Implements a basic file system backed data store (using a json
file), to store keyboard classification information persisted
across reboots.

Test: atest --host libinput_rust_test
Bug: 263559234
Flag: com.android.input.flags.enable_keyboard_classifier

Change-Id: I521444342ae4e98191262273b881775fb2157ef2
parent 8b426bf4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ rust_defaults {
        "liblogger",
        "liblog_rust",
        "inputconstants-rust",
        "libserde",
        "libserde_json",
    ],
    whole_static_libs: [
        "libinput_from_rust_to_cpp",
+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;
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -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,
+95 −50
Original line number Diff line number Diff line
@@ -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);
        }
    }

    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,
        }
    }
@@ -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,
        }
    }
+10 −1
Original line number Diff line number Diff line
@@ -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(