blob: 3c789b41e26dd4b481711c8c61cb3c5b063212d5 [file] [log] [blame]
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +00001/*
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.
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000020//!
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).
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000034
Vaibhav Devmurari099fb592024-06-26 14:26:30 +000035use crate::data_store::DataStore;
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000036use crate::input::{DeviceId, InputDevice, KeyboardType};
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +000037use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000038use crate::{DeviceClass, ModifierState};
39use std::collections::HashMap;
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000040
41/// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic
42/// keyboard or non-alphabetic keyboard
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000043pub struct KeyboardClassifier {
44 device_map: HashMap<DeviceId, KeyboardInfo>,
Vaibhav Devmurari099fb592024-06-26 14:26:30 +000045 data_store: DataStore,
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000046}
47
48struct KeyboardInfo {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +000049 device: InputDevice,
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000050 keyboard_type: KeyboardType,
51 is_finalized: bool,
52}
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000053
54impl KeyboardClassifier {
55 /// Create a new KeyboardClassifier
Vaibhav Devmurari099fb592024-06-26 14:26:30 +000056 pub fn new(data_store: DataStore) -> Self {
57 Self { device_map: HashMap::new(), data_store }
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000058 }
59
60 /// Adds keyboard to KeyboardClassifier
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000061 pub fn notify_keyboard_changed(&mut self, device: InputDevice) {
62 let (keyboard_type, is_finalized) = self.classify_keyboard(&device);
Vaibhav Devmurari099fb592024-06-26 14:26:30 +000063 self.device_map
64 .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized });
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000065 }
66
67 /// Get keyboard type for a tracked keyboard in KeyboardClassifier
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000068 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 };
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000074 }
75
76 /// Tells if keyboard type classification is finalized. Once finalized the classification can't
77 /// change until device is reconnected again.
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000078 ///
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
81 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 };
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000087 }
88
89 /// Process a key event and change keyboard type if required.
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000090 /// - 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
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000092 pub fn process_key(
93 &mut self,
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000094 device_id: DeviceId,
95 evdev_code: i32,
96 modifier_state: ModifierState,
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +000097 ) {
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +000098 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;
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000107 self.data_store.set_keyboard_type(
108 &keyboard.device.identifier.descriptor,
109 keyboard.keyboard_type,
110 keyboard.is_finalized,
111 );
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000112 }
113 }
114 }
115
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000116 fn classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool) {
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000117 // 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 }
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000132
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000133 // 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
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000140 // Check in known device list for classification
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000141 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);
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000144 }
145 }
146
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000147 // 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
177 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)]
188mod tests {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000189 use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore};
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000190 use crate::input::{DeviceId, InputDevice, KeyboardType};
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000191 use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000192 use crate::keyboard_classifier::KeyboardClassifier;
193 use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
194
195 static DEVICE_ID: DeviceId = DeviceId(1);
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000196 static SECOND_DEVICE_ID: DeviceId = DeviceId(2);
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000197 static KEY_A: i32 = 30;
198 static KEY_1: i32 = 2;
199
200 #[test]
201 fn classify_external_alphabetic_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000202 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000203 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]
211 fn classify_external_non_alphabetic_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000212 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000213 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]
220 fn classify_mouse_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000221 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000222 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]
233 fn classify_touchpad_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000234 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000235 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]
246 fn classify_stylus_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000247 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000248 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]
259 fn classify_dpad_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000260 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000261 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]
272 fn classify_joystick_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000273 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000274 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]
285 fn classify_gamepad_pretending_as_keyboard() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000286 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000287 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]
298 fn reclassify_keyboard_on_alphabetic_key_event() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000299 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000300 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]
316 fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000317 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000318 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]
334 fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000335 let mut classifier = create_classifier();
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000336 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
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000350 #[test]
351 fn classify_known_devices() {
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000352 let mut classifier = create_classifier();
353 for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000354 classifier
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000355 .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]
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
396 fn create_classifier() -> KeyboardClassifier {
397 KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new())))
398 }
399
400 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(),
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000410 }
411 }
412
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000413 fn create_device(classes: DeviceClass) -> InputDevice {
414 InputDevice {
415 device_id: DEVICE_ID,
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000416 identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
Vaibhav Devmurari2e73b2a2024-06-07 17:45:19 +0000417 classes,
418 }
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +0000419 }
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000420
421 fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
422 InputDevice {
423 device_id: DEVICE_ID,
Vaibhav Devmurari099fb592024-06-26 14:26:30 +0000424 identifier: create_identifier(vendor, product),
Vaibhav Devmuraria8359fc2024-06-10 20:01:25 +0000425 classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
426 }
427 }
Vaibhav Devmurarie58ffb92024-05-22 17:38:25 +0000428}