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 //! TODO(b/263559234): Data store implementation to store information about past classification
36 
37 use crate::input::{DeviceId, InputDevice, KeyboardType};
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 #[derive(Default)]
44 pub struct KeyboardClassifier {
45     device_map: HashMap<DeviceId, KeyboardInfo>,
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() -> Self56     pub fn new() -> Self {
57         Default::default()
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.insert(
64             device.device_id,
65             KeyboardInfo { _device: device, keyboard_type, is_finalized },
66         );
67     }
68 
69     /// Get keyboard type for a tracked keyboard in KeyboardClassifier
get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType70     pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType {
71         return if let Some(keyboard) = self.device_map.get(&device_id) {
72             keyboard.keyboard_type
73         } else {
74             KeyboardType::None
75         };
76     }
77 
78     /// Tells if keyboard type classification is finalized. Once finalized the classification can't
79     /// change until device is reconnected again.
80     ///
81     /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or
82     /// allowlist that are explicitly categorized and won't change with future key events
is_finalized(&self, device_id: DeviceId) -> bool83     pub fn is_finalized(&self, device_id: DeviceId) -> bool {
84         return if let Some(keyboard) = self.device_map.get(&device_id) {
85             keyboard.is_finalized
86         } else {
87             false
88         };
89     }
90 
91     /// Process a key event and change keyboard type if required.
92     /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic
93     /// - 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, )94     pub fn process_key(
95         &mut self,
96         device_id: DeviceId,
97         evdev_code: i32,
98         modifier_state: ModifierState,
99     ) {
100         if let Some(keyboard) = self.device_map.get_mut(&device_id) {
101             // Ignore all key events with modifier state since they can be macro shortcuts used by
102             // some non-keyboard peripherals like TV remotes, game controllers, etc.
103             if modifier_state.bits() != 0 {
104                 return;
105             }
106             if Self::is_alphabetic_key(&evdev_code) {
107                 keyboard.keyboard_type = KeyboardType::Alphabetic;
108                 keyboard.is_finalized = true;
109             }
110         }
111     }
112 
classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool)113     fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) {
114         // This should never happen but having keyboard device class is necessary to be classified
115         // as any type of keyboard.
116         if !device.classes.contains(DeviceClass::Keyboard) {
117             return (KeyboardType::None, true);
118         }
119         // Normal classification for internal and virtual keyboards
120         if !device.classes.contains(DeviceClass::External)
121             || device.classes.contains(DeviceClass::Virtual)
122         {
123             return if device.classes.contains(DeviceClass::AlphabeticKey) {
124                 (KeyboardType::Alphabetic, true)
125             } else {
126                 (KeyboardType::NonAlphabetic, true)
127             };
128         }
129         // Any composite device with multiple device classes should be categorized as non-alphabetic
130         // keyboard initially
131         if device.classes.contains(DeviceClass::Touch)
132             || device.classes.contains(DeviceClass::Cursor)
133             || device.classes.contains(DeviceClass::MultiTouch)
134             || device.classes.contains(DeviceClass::ExternalStylus)
135             || device.classes.contains(DeviceClass::Touchpad)
136             || device.classes.contains(DeviceClass::Dpad)
137             || device.classes.contains(DeviceClass::Gamepad)
138             || device.classes.contains(DeviceClass::Switch)
139             || device.classes.contains(DeviceClass::Joystick)
140             || device.classes.contains(DeviceClass::RotaryEncoder)
141         {
142             // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
143             // kernel, we no longer need to process key events to verify.
144             return (
145                 KeyboardType::NonAlphabetic,
146                 !device.classes.contains(DeviceClass::AlphabeticKey),
147             );
148         }
149         // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard
150         if device.classes.contains(DeviceClass::AlphabeticKey) {
151             (KeyboardType::Alphabetic, true)
152         } else {
153             // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
154             // kernel, we no longer need to process key events to verify.
155             (KeyboardType::NonAlphabetic, true)
156         }
157     }
158 
is_alphabetic_key(evdev_code: &i32) -> bool159     fn is_alphabetic_key(evdev_code: &i32) -> bool {
160         // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ])
161         (16..=27).contains(evdev_code)
162             // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `)
163             || (30..=41).contains(evdev_code)
164             // Keyboard alphabetic row 3 (\ Z X C V B N M , . /)
165             || (43..=53).contains(evdev_code)
166     }
167 }
168 
169 #[cfg(test)]
170 mod tests {
171     use crate::input::{DeviceId, InputDevice, KeyboardType};
172     use crate::keyboard_classifier::KeyboardClassifier;
173     use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
174 
175     static DEVICE_ID: DeviceId = DeviceId(1);
176     static KEY_A: i32 = 30;
177     static KEY_1: i32 = 2;
178 
179     #[test]
classify_external_alphabetic_keyboard()180     fn classify_external_alphabetic_keyboard() {
181         let mut classifier = KeyboardClassifier::new();
182         classifier.notify_keyboard_changed(create_device(
183             DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
184         ));
185         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
186         assert!(classifier.is_finalized(DEVICE_ID));
187     }
188 
189     #[test]
classify_external_non_alphabetic_keyboard()190     fn classify_external_non_alphabetic_keyboard() {
191         let mut classifier = KeyboardClassifier::new();
192         classifier
193             .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External));
194         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
195         assert!(classifier.is_finalized(DEVICE_ID));
196     }
197 
198     #[test]
classify_mouse_pretending_as_keyboard()199     fn classify_mouse_pretending_as_keyboard() {
200         let mut classifier = KeyboardClassifier::new();
201         classifier.notify_keyboard_changed(create_device(
202             DeviceClass::Keyboard
203                 | DeviceClass::Cursor
204                 | DeviceClass::AlphabeticKey
205                 | DeviceClass::External,
206         ));
207         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
208         assert!(!classifier.is_finalized(DEVICE_ID));
209     }
210 
211     #[test]
classify_touchpad_pretending_as_keyboard()212     fn classify_touchpad_pretending_as_keyboard() {
213         let mut classifier = KeyboardClassifier::new();
214         classifier.notify_keyboard_changed(create_device(
215             DeviceClass::Keyboard
216                 | DeviceClass::Touchpad
217                 | DeviceClass::AlphabeticKey
218                 | DeviceClass::External,
219         ));
220         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
221         assert!(!classifier.is_finalized(DEVICE_ID));
222     }
223 
224     #[test]
classify_stylus_pretending_as_keyboard()225     fn classify_stylus_pretending_as_keyboard() {
226         let mut classifier = KeyboardClassifier::new();
227         classifier.notify_keyboard_changed(create_device(
228             DeviceClass::Keyboard
229                 | DeviceClass::ExternalStylus
230                 | DeviceClass::AlphabeticKey
231                 | DeviceClass::External,
232         ));
233         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
234         assert!(!classifier.is_finalized(DEVICE_ID));
235     }
236 
237     #[test]
classify_dpad_pretending_as_keyboard()238     fn classify_dpad_pretending_as_keyboard() {
239         let mut classifier = KeyboardClassifier::new();
240         classifier.notify_keyboard_changed(create_device(
241             DeviceClass::Keyboard
242                 | DeviceClass::Dpad
243                 | DeviceClass::AlphabeticKey
244                 | DeviceClass::External,
245         ));
246         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
247         assert!(!classifier.is_finalized(DEVICE_ID));
248     }
249 
250     #[test]
classify_joystick_pretending_as_keyboard()251     fn classify_joystick_pretending_as_keyboard() {
252         let mut classifier = KeyboardClassifier::new();
253         classifier.notify_keyboard_changed(create_device(
254             DeviceClass::Keyboard
255                 | DeviceClass::Joystick
256                 | DeviceClass::AlphabeticKey
257                 | DeviceClass::External,
258         ));
259         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
260         assert!(!classifier.is_finalized(DEVICE_ID));
261     }
262 
263     #[test]
classify_gamepad_pretending_as_keyboard()264     fn classify_gamepad_pretending_as_keyboard() {
265         let mut classifier = KeyboardClassifier::new();
266         classifier.notify_keyboard_changed(create_device(
267             DeviceClass::Keyboard
268                 | DeviceClass::Gamepad
269                 | DeviceClass::AlphabeticKey
270                 | DeviceClass::External,
271         ));
272         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
273         assert!(!classifier.is_finalized(DEVICE_ID));
274     }
275 
276     #[test]
reclassify_keyboard_on_alphabetic_key_event()277     fn reclassify_keyboard_on_alphabetic_key_event() {
278         let mut classifier = KeyboardClassifier::new();
279         classifier.notify_keyboard_changed(create_device(
280             DeviceClass::Keyboard
281                 | DeviceClass::Dpad
282                 | DeviceClass::AlphabeticKey
283                 | DeviceClass::External,
284         ));
285         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
286         assert!(!classifier.is_finalized(DEVICE_ID));
287 
288         // on alphabetic key event
289         classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
290         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
291         assert!(classifier.is_finalized(DEVICE_ID));
292     }
293 
294     #[test]
dont_reclassify_keyboard_on_non_alphabetic_key_event()295     fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
296         let mut classifier = KeyboardClassifier::new();
297         classifier.notify_keyboard_changed(create_device(
298             DeviceClass::Keyboard
299                 | DeviceClass::Dpad
300                 | DeviceClass::AlphabeticKey
301                 | DeviceClass::External,
302         ));
303         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
304         assert!(!classifier.is_finalized(DEVICE_ID));
305 
306         // on number key event
307         classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None);
308         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
309         assert!(!classifier.is_finalized(DEVICE_ID));
310     }
311 
312     #[test]
dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers()313     fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
314         let mut classifier = KeyboardClassifier::new();
315         classifier.notify_keyboard_changed(create_device(
316             DeviceClass::Keyboard
317                 | DeviceClass::Dpad
318                 | DeviceClass::AlphabeticKey
319                 | DeviceClass::External,
320         ));
321         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
322         assert!(!classifier.is_finalized(DEVICE_ID));
323 
324         classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn);
325         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
326         assert!(!classifier.is_finalized(DEVICE_ID));
327     }
328 
create_device(classes: DeviceClass) -> InputDevice329     fn create_device(classes: DeviceClass) -> InputDevice {
330         InputDevice {
331             device_id: DEVICE_ID,
332             identifier: RustInputDeviceIdentifier {
333                 name: "test_device".to_string(),
334                 location: "location".to_string(),
335                 unique_id: "unique_id".to_string(),
336                 bus: 123,
337                 vendor: 234,
338                 product: 345,
339                 version: 567,
340                 descriptor: "descriptor".to_string(),
341             },
342             classes,
343         }
344     }
345 }
346