xref: /aosp_15_r20/frameworks/native/libs/input/rust/keyboard_classifier.rs (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! Contains the KeyboardClassifier, that tries to identify whether an Input device is an
18 //! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device
19 //! in order to verify/change the inferred keyboard type.
20 //!
21 //! Initial classification:
22 //! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad,
23 //!   Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic
24 //! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic
25 //! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic
26 //!
27 //! On process keys:
28 //! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to
29 //!   KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true)
30 //! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event
31 //!    across multiple device connections in a time period, then change type to
32 //!    KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic
33 //!    (i.e. verified = false).
34 
35 use crate::data_store::DataStore;
36 use crate::input::{DeviceId, InputDevice, KeyboardType};
37 use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
38 use crate::{DeviceClass, ModifierState};
39 use std::collections::HashMap;
40 
41 /// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic
42 /// keyboard or non-alphabetic keyboard
43 pub struct KeyboardClassifier {
44     device_map: HashMap<DeviceId, KeyboardInfo>,
45     data_store: DataStore,
46 }
47 
48 struct KeyboardInfo {
49     device: InputDevice,
50     keyboard_type: KeyboardType,
51     is_finalized: bool,
52 }
53 
54 impl KeyboardClassifier {
55     /// Create a new KeyboardClassifier
new(data_store: DataStore) -> Self56     pub fn new(data_store: DataStore) -> Self {
57         Self { device_map: HashMap::new(), data_store }
58     }
59 
60     /// Adds keyboard to KeyboardClassifier
notify_keyboard_changed(&mut self, device: InputDevice)61     pub fn notify_keyboard_changed(&mut self, device: InputDevice) {
62         let (keyboard_type, is_finalized) = self.classify_keyboard(&device);
63         self.device_map
64             .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized });
65     }
66 
67     /// Get keyboard type for a tracked keyboard in KeyboardClassifier
get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType68     pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType {
69         return if let Some(keyboard) = self.device_map.get(&device_id) {
70             keyboard.keyboard_type
71         } else {
72             KeyboardType::None
73         };
74     }
75 
76     /// Tells if keyboard type classification is finalized. Once finalized the classification can't
77     /// change until device is reconnected again.
78     ///
79     /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or
80     /// allowlist that are explicitly categorized and won't change with future key events
is_finalized(&self, device_id: DeviceId) -> bool81     pub fn is_finalized(&self, device_id: DeviceId) -> bool {
82         return if let Some(keyboard) = self.device_map.get(&device_id) {
83             keyboard.is_finalized
84         } else {
85             false
86         };
87     }
88 
89     /// Process a key event and change keyboard type if required.
90     /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic
91     /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic
process_key( &mut self, device_id: DeviceId, evdev_code: i32, modifier_state: ModifierState, )92     pub fn process_key(
93         &mut self,
94         device_id: DeviceId,
95         evdev_code: i32,
96         modifier_state: ModifierState,
97     ) {
98         if let Some(keyboard) = self.device_map.get_mut(&device_id) {
99             // Ignore all key events with modifier state since they can be macro shortcuts used by
100             // some non-keyboard peripherals like TV remotes, game controllers, etc.
101             if modifier_state.bits() != 0 {
102                 return;
103             }
104             if Self::is_alphabetic_key(&evdev_code) {
105                 keyboard.keyboard_type = KeyboardType::Alphabetic;
106                 keyboard.is_finalized = true;
107                 self.data_store.set_keyboard_type(
108                     &keyboard.device.identifier.descriptor,
109                     keyboard.keyboard_type,
110                     keyboard.is_finalized,
111                 );
112             }
113         }
114     }
115 
classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool)116     fn classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool) {
117         // This should never happen but having keyboard device class is necessary to be classified
118         // as any type of keyboard.
119         if !device.classes.contains(DeviceClass::Keyboard) {
120             return (KeyboardType::None, true);
121         }
122         // Normal classification for internal and virtual keyboards
123         if !device.classes.contains(DeviceClass::External)
124             || device.classes.contains(DeviceClass::Virtual)
125         {
126             return if device.classes.contains(DeviceClass::AlphabeticKey) {
127                 (KeyboardType::Alphabetic, true)
128             } else {
129                 (KeyboardType::NonAlphabetic, true)
130             };
131         }
132 
133         // Check in data store
134         if let Some((keyboard_type, is_finalized)) =
135             self.data_store.get_keyboard_type(&device.identifier.descriptor)
136         {
137             return (keyboard_type, is_finalized);
138         }
139 
140         // Check in known device list for classification
141         for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
142             if device.identifier.vendor == *vendor && device.identifier.product == *product {
143                 return (*keyboard_type, *is_finalized);
144             }
145         }
146 
147         // Any composite device with multiple device classes should be categorized as non-alphabetic
148         // keyboard initially
149         if device.classes.contains(DeviceClass::Touch)
150             || device.classes.contains(DeviceClass::Cursor)
151             || device.classes.contains(DeviceClass::MultiTouch)
152             || device.classes.contains(DeviceClass::ExternalStylus)
153             || device.classes.contains(DeviceClass::Touchpad)
154             || device.classes.contains(DeviceClass::Dpad)
155             || device.classes.contains(DeviceClass::Gamepad)
156             || device.classes.contains(DeviceClass::Switch)
157             || device.classes.contains(DeviceClass::Joystick)
158             || device.classes.contains(DeviceClass::RotaryEncoder)
159         {
160             // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
161             // kernel, we no longer need to process key events to verify.
162             return (
163                 KeyboardType::NonAlphabetic,
164                 !device.classes.contains(DeviceClass::AlphabeticKey),
165             );
166         }
167         // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard
168         if device.classes.contains(DeviceClass::AlphabeticKey) {
169             (KeyboardType::Alphabetic, true)
170         } else {
171             // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
172             // kernel, we no longer need to process key events to verify.
173             (KeyboardType::NonAlphabetic, true)
174         }
175     }
176 
is_alphabetic_key(evdev_code: &i32) -> bool177     fn is_alphabetic_key(evdev_code: &i32) -> bool {
178         // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ])
179         (16..=27).contains(evdev_code)
180             // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `)
181             || (30..=41).contains(evdev_code)
182             // Keyboard alphabetic row 3 (\ Z X C V B N M , . /)
183             || (43..=53).contains(evdev_code)
184     }
185 }
186 
187 #[cfg(test)]
188 mod tests {
189     use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore};
190     use crate::input::{DeviceId, InputDevice, KeyboardType};
191     use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
192     use crate::keyboard_classifier::KeyboardClassifier;
193     use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
194 
195     static DEVICE_ID: DeviceId = DeviceId(1);
196     static SECOND_DEVICE_ID: DeviceId = DeviceId(2);
197     static KEY_A: i32 = 30;
198     static KEY_1: i32 = 2;
199 
200     #[test]
classify_external_alphabetic_keyboard()201     fn classify_external_alphabetic_keyboard() {
202         let mut classifier = create_classifier();
203         classifier.notify_keyboard_changed(create_device(
204             DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
205         ));
206         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
207         assert!(classifier.is_finalized(DEVICE_ID));
208     }
209 
210     #[test]
classify_external_non_alphabetic_keyboard()211     fn classify_external_non_alphabetic_keyboard() {
212         let mut classifier = create_classifier();
213         classifier
214             .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External));
215         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
216         assert!(classifier.is_finalized(DEVICE_ID));
217     }
218 
219     #[test]
classify_mouse_pretending_as_keyboard()220     fn classify_mouse_pretending_as_keyboard() {
221         let mut classifier = create_classifier();
222         classifier.notify_keyboard_changed(create_device(
223             DeviceClass::Keyboard
224                 | DeviceClass::Cursor
225                 | DeviceClass::AlphabeticKey
226                 | DeviceClass::External,
227         ));
228         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
229         assert!(!classifier.is_finalized(DEVICE_ID));
230     }
231 
232     #[test]
classify_touchpad_pretending_as_keyboard()233     fn classify_touchpad_pretending_as_keyboard() {
234         let mut classifier = create_classifier();
235         classifier.notify_keyboard_changed(create_device(
236             DeviceClass::Keyboard
237                 | DeviceClass::Touchpad
238                 | DeviceClass::AlphabeticKey
239                 | DeviceClass::External,
240         ));
241         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
242         assert!(!classifier.is_finalized(DEVICE_ID));
243     }
244 
245     #[test]
classify_stylus_pretending_as_keyboard()246     fn classify_stylus_pretending_as_keyboard() {
247         let mut classifier = create_classifier();
248         classifier.notify_keyboard_changed(create_device(
249             DeviceClass::Keyboard
250                 | DeviceClass::ExternalStylus
251                 | DeviceClass::AlphabeticKey
252                 | DeviceClass::External,
253         ));
254         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
255         assert!(!classifier.is_finalized(DEVICE_ID));
256     }
257 
258     #[test]
classify_dpad_pretending_as_keyboard()259     fn classify_dpad_pretending_as_keyboard() {
260         let mut classifier = create_classifier();
261         classifier.notify_keyboard_changed(create_device(
262             DeviceClass::Keyboard
263                 | DeviceClass::Dpad
264                 | DeviceClass::AlphabeticKey
265                 | DeviceClass::External,
266         ));
267         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
268         assert!(!classifier.is_finalized(DEVICE_ID));
269     }
270 
271     #[test]
classify_joystick_pretending_as_keyboard()272     fn classify_joystick_pretending_as_keyboard() {
273         let mut classifier = create_classifier();
274         classifier.notify_keyboard_changed(create_device(
275             DeviceClass::Keyboard
276                 | DeviceClass::Joystick
277                 | DeviceClass::AlphabeticKey
278                 | DeviceClass::External,
279         ));
280         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
281         assert!(!classifier.is_finalized(DEVICE_ID));
282     }
283 
284     #[test]
classify_gamepad_pretending_as_keyboard()285     fn classify_gamepad_pretending_as_keyboard() {
286         let mut classifier = create_classifier();
287         classifier.notify_keyboard_changed(create_device(
288             DeviceClass::Keyboard
289                 | DeviceClass::Gamepad
290                 | DeviceClass::AlphabeticKey
291                 | DeviceClass::External,
292         ));
293         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
294         assert!(!classifier.is_finalized(DEVICE_ID));
295     }
296 
297     #[test]
reclassify_keyboard_on_alphabetic_key_event()298     fn reclassify_keyboard_on_alphabetic_key_event() {
299         let mut classifier = create_classifier();
300         classifier.notify_keyboard_changed(create_device(
301             DeviceClass::Keyboard
302                 | DeviceClass::Dpad
303                 | DeviceClass::AlphabeticKey
304                 | DeviceClass::External,
305         ));
306         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
307         assert!(!classifier.is_finalized(DEVICE_ID));
308 
309         // on alphabetic key event
310         classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
311         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
312         assert!(classifier.is_finalized(DEVICE_ID));
313     }
314 
315     #[test]
dont_reclassify_keyboard_on_non_alphabetic_key_event()316     fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
317         let mut classifier = create_classifier();
318         classifier.notify_keyboard_changed(create_device(
319             DeviceClass::Keyboard
320                 | DeviceClass::Dpad
321                 | DeviceClass::AlphabeticKey
322                 | DeviceClass::External,
323         ));
324         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
325         assert!(!classifier.is_finalized(DEVICE_ID));
326 
327         // on number key event
328         classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None);
329         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
330         assert!(!classifier.is_finalized(DEVICE_ID));
331     }
332 
333     #[test]
dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers()334     fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
335         let mut classifier = create_classifier();
336         classifier.notify_keyboard_changed(create_device(
337             DeviceClass::Keyboard
338                 | DeviceClass::Dpad
339                 | DeviceClass::AlphabeticKey
340                 | DeviceClass::External,
341         ));
342         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
343         assert!(!classifier.is_finalized(DEVICE_ID));
344 
345         classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn);
346         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
347         assert!(!classifier.is_finalized(DEVICE_ID));
348     }
349 
350     #[test]
classify_known_devices()351     fn classify_known_devices() {
352         let mut classifier = create_classifier();
353         for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
354             classifier
355                 .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product));
356             assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type);
357             assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized);
358         }
359     }
360 
361     #[test]
classify_previously_reclassified_devices()362     fn classify_previously_reclassified_devices() {
363         let test_reader_writer = TestFileReaderWriter::new();
364         {
365             let mut classifier =
366                 KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
367             let device = create_device(
368                 DeviceClass::Keyboard
369                     | DeviceClass::Dpad
370                     | DeviceClass::AlphabeticKey
371                     | DeviceClass::External,
372             );
373             classifier.notify_keyboard_changed(device);
374             classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
375         }
376 
377         // Re-create classifier and data store to mimic a reboot (but use the same file system
378         // reader writer)
379         {
380             let mut classifier =
381                 KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
382             let device = InputDevice {
383                 device_id: SECOND_DEVICE_ID,
384                 identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
385                 classes: DeviceClass::Keyboard
386                     | DeviceClass::Dpad
387                     | DeviceClass::AlphabeticKey
388                     | DeviceClass::External,
389             };
390             classifier.notify_keyboard_changed(device);
391             assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic);
392             assert!(classifier.is_finalized(SECOND_DEVICE_ID));
393         }
394     }
395 
create_classifier() -> KeyboardClassifier396     fn create_classifier() -> KeyboardClassifier {
397         KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new())))
398     }
399 
create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier400     fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier {
401         RustInputDeviceIdentifier {
402             name: "test_device".to_string(),
403             location: "location".to_string(),
404             unique_id: "unique_id".to_string(),
405             bus: 123,
406             vendor,
407             product,
408             version: 567,
409             descriptor: "descriptor".to_string(),
410         }
411     }
412 
create_device(classes: DeviceClass) -> InputDevice413     fn create_device(classes: DeviceClass) -> InputDevice {
414         InputDevice {
415             device_id: DEVICE_ID,
416             identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
417             classes,
418         }
419     }
420 
create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice421     fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
422         InputDevice {
423             device_id: DEVICE_ID,
424             identifier: create_identifier(vendor, product),
425             classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
426         }
427     }
428 }
429