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