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

Commit f5d714c6 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Android (Google) Code Review
Browse files

Merge "Create basic data store to store keyboard classification" into main

parents b5f2c709 099fb592
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(