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