| /* |
| * Copyright 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //! Contains the InputVerifier, used to validate a stream of input events. |
| |
| use crate::ffi::RustPointerProperties; |
| use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass}; |
| use log::info; |
| use std::collections::HashMap; |
| use std::collections::HashSet; |
| |
| fn verify_event( |
| action: MotionAction, |
| pointer_properties: &[RustPointerProperties], |
| flags: &MotionFlags, |
| ) -> Result<(), String> { |
| let pointer_count = pointer_properties.len(); |
| if pointer_count < 1 { |
| return Err(format!("Invalid {} event: no pointers", action)); |
| } |
| match action { |
| MotionAction::Down |
| | MotionAction::HoverEnter |
| | MotionAction::HoverExit |
| | MotionAction::HoverMove |
| | MotionAction::Up => { |
| if pointer_count != 1 { |
| return Err(format!( |
| "Invalid {} event: there are {} pointers in the event", |
| action, pointer_count |
| )); |
| } |
| } |
| |
| MotionAction::Cancel => { |
| if !flags.contains(MotionFlags::CANCELED) { |
| return Err(format!( |
| "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}", |
| flags |
| )); |
| } |
| } |
| |
| MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => { |
| if action_index >= pointer_count { |
| return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count)); |
| } |
| } |
| |
| _ => {} |
| } |
| Ok(()) |
| } |
| |
| /// The InputVerifier is used to validate a stream of input events. |
| pub struct InputVerifier { |
| name: String, |
| should_log: bool, |
| touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>, |
| hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>, |
| } |
| |
| impl InputVerifier { |
| /// Create a new InputVerifier. |
| pub fn new(name: &str, should_log: bool) -> Self { |
| logger::init( |
| logger::Config::default() |
| .with_tag_on_device("InputVerifier") |
| .with_max_level(log::LevelFilter::Trace), |
| ); |
| Self { |
| name: name.to_owned(), |
| should_log, |
| touching_pointer_ids_by_device: HashMap::new(), |
| hovering_pointer_ids_by_device: HashMap::new(), |
| } |
| } |
| |
| /// Process a pointer movement event from an InputDevice. |
| /// If the event is not valid, we return an error string that describes the issue. |
| pub fn process_movement( |
| &mut self, |
| device_id: DeviceId, |
| source: Source, |
| action: u32, |
| pointer_properties: &[RustPointerProperties], |
| flags: MotionFlags, |
| ) -> Result<(), String> { |
| if !source.is_from_class(SourceClass::Pointer) { |
| // Skip non-pointer sources like MOUSE_RELATIVE for now |
| return Ok(()); |
| } |
| if self.should_log { |
| info!( |
| "Processing {} for device {:?} ({} pointer{}) on {}", |
| MotionAction::from(action).to_string(), |
| device_id, |
| pointer_properties.len(), |
| if pointer_properties.len() == 1 { "" } else { "s" }, |
| self.name |
| ); |
| } |
| |
| verify_event(action.into(), pointer_properties, &flags)?; |
| |
| match action.into() { |
| MotionAction::Down => { |
| if self.touching_pointer_ids_by_device.contains_key(&device_id) { |
| return Err(format!( |
| "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}", |
| self.name, device_id, self.touching_pointer_ids_by_device |
| )); |
| } |
| let it = self.touching_pointer_ids_by_device.entry(device_id).or_default(); |
| it.insert(pointer_properties[0].id); |
| } |
| MotionAction::PointerDown { action_index } => { |
| if !self.touching_pointer_ids_by_device.contains_key(&device_id) { |
| return Err(format!( |
| "{}: Received POINTER_DOWN but no pointers are currently down \ |
| for device {:?}", |
| self.name, device_id |
| )); |
| } |
| let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap(); |
| if it.len() != pointer_properties.len() - 1 { |
| return Err(format!( |
| "{}: There are currently {} touching pointers, but the incoming \ |
| POINTER_DOWN event has {}", |
| self.name, |
| it.len(), |
| pointer_properties.len() |
| )); |
| } |
| let pointer_id = pointer_properties[action_index].id; |
| if it.contains(&pointer_id) { |
| return Err(format!( |
| "{}: Pointer with id={} already present found in the properties", |
| self.name, pointer_id |
| )); |
| } |
| it.insert(pointer_id); |
| } |
| MotionAction::Move => { |
| if !self.ensure_touching_pointers_match(device_id, pointer_properties) { |
| return Err(format!( |
| "{}: ACTION_MOVE touching pointers don't match", |
| self.name |
| )); |
| } |
| } |
| MotionAction::PointerUp { action_index } => { |
| if !self.ensure_touching_pointers_match(device_id, pointer_properties) { |
| return Err(format!( |
| "{}: ACTION_POINTER_UP touching pointers don't match", |
| self.name |
| )); |
| } |
| let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap(); |
| let pointer_id = pointer_properties[action_index].id; |
| it.remove(&pointer_id); |
| } |
| MotionAction::Up => { |
| if !self.touching_pointer_ids_by_device.contains_key(&device_id) { |
| return Err(format!( |
| "{} Received ACTION_UP but no pointers are currently down for device {:?}", |
| self.name, device_id |
| )); |
| } |
| let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap(); |
| if it.len() != 1 { |
| return Err(format!( |
| "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}", |
| self.name, it, device_id |
| )); |
| } |
| let pointer_id = pointer_properties[0].id; |
| if !it.contains(&pointer_id) { |
| return Err(format!( |
| "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\ |
| {:?} for device {:?}", |
| self.name, pointer_id, it, device_id |
| )); |
| } |
| self.touching_pointer_ids_by_device.remove(&device_id); |
| } |
| MotionAction::Cancel => { |
| if !self.ensure_touching_pointers_match(device_id, pointer_properties) { |
| return Err(format!( |
| "{}: Got ACTION_CANCEL, but the pointers don't match. \ |
| Existing pointers: {:?}", |
| self.name, self.touching_pointer_ids_by_device |
| )); |
| } |
| self.touching_pointer_ids_by_device.remove(&device_id); |
| } |
| /* |
| * The hovering protocol currently supports a single pointer only, because we do not |
| * have ACTION_HOVER_POINTER_ENTER or ACTION_HOVER_POINTER_EXIT. |
| * Still, we are keeping the infrastructure here pretty general in case that is |
| * eventually supported. |
| */ |
| MotionAction::HoverEnter => { |
| if self.hovering_pointer_ids_by_device.contains_key(&device_id) { |
| return Err(format!( |
| "{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\ |
| {:?}", |
| self.name, device_id, self.hovering_pointer_ids_by_device |
| )); |
| } |
| let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default(); |
| it.insert(pointer_properties[0].id); |
| } |
| MotionAction::HoverMove => { |
| // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER. |
| // If there was no prior HOVER_ENTER, just start a new hovering pointer. |
| let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default(); |
| it.insert(pointer_properties[0].id); |
| } |
| MotionAction::HoverExit => { |
| if !self.hovering_pointer_ids_by_device.contains_key(&device_id) { |
| return Err(format!( |
| "{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}", |
| self.name, device_id |
| )); |
| } |
| let pointer_id = pointer_properties[0].id; |
| let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap(); |
| it.remove(&pointer_id); |
| |
| if !it.is_empty() { |
| return Err(format!( |
| "{}: Removed hovering pointer {}, but pointers are still\ |
| hovering for device {:?}: {:?}", |
| self.name, pointer_id, device_id, it |
| )); |
| } |
| self.hovering_pointer_ids_by_device.remove(&device_id); |
| } |
| _ => return Ok(()), |
| } |
| Ok(()) |
| } |
| |
| /// Notify the verifier that the device has been reset, which will cause the verifier to erase |
| /// the current internal state for this device. Subsequent events from this device are expected |
| //// to start a new gesture. |
| pub fn reset_device(&mut self, device_id: DeviceId) { |
| self.touching_pointer_ids_by_device.remove(&device_id); |
| self.hovering_pointer_ids_by_device.remove(&device_id); |
| } |
| |
| fn ensure_touching_pointers_match( |
| &self, |
| device_id: DeviceId, |
| pointer_properties: &[RustPointerProperties], |
| ) -> bool { |
| let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else { |
| return false; |
| }; |
| |
| if pointers.len() != pointer_properties.len() { |
| return false; |
| } |
| |
| for pointer_property in pointer_properties.iter() { |
| let pointer_id = pointer_property.id; |
| if !pointers.contains(&pointer_id) { |
| return false; |
| } |
| } |
| true |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::input_verifier::InputVerifier; |
| use crate::DeviceId; |
| use crate::MotionFlags; |
| use crate::RustPointerProperties; |
| use crate::Source; |
| |
| #[test] |
| /** |
| * Send a DOWN event with 2 pointers and ensure that it's marked as invalid. |
| */ |
| fn bad_down_event() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ true); |
| let pointer_properties = |
| Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_err()); |
| } |
| |
| #[test] |
| fn single_pointer_stream() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_UP, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn two_pointer_stream() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| // POINTER 1 DOWN |
| let two_pointer_properties = |
| Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN |
| | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), |
| &two_pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| // POINTER 0 UP |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP |
| | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), |
| &two_pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| // ACTION_UP for pointer id=1 |
| let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_UP, |
| &pointer_1_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn multi_device_stream() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(2), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(2), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_UP, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn action_cancel() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_CANCEL, |
| &pointer_properties, |
| MotionFlags::CANCELED, |
| ) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn invalid_action_cancel() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_CANCEL, |
| &pointer_properties, |
| MotionFlags::empty(), // forgot to set FLAG_CANCELED |
| ) |
| .is_err()); |
| } |
| |
| #[test] |
| fn invalid_up() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_UP, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_err()); |
| } |
| |
| #[test] |
| fn correct_hover_sequence() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn double_hover_enter() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_err()); |
| } |
| |
| // Send a MOVE without a preceding DOWN event. This is OK because it's from source |
| // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event. |
| #[test] |
| fn relative_mouse_move() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(2), |
| Source::MouseRelative, |
| input_bindgen::AMOTION_EVENT_ACTION_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| } |
| |
| // Send a MOVE event with incorrect number of pointers (one of the pointers is missing). |
| #[test] |
| fn move_with_wrong_number_of_pointers() { |
| let mut verifier = InputVerifier::new("Test", /*should_log*/ false); |
| let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_DOWN, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| // POINTER 1 DOWN |
| let two_pointer_properties = |
| Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]); |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN |
| | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), |
| &two_pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_ok()); |
| // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected |
| assert!(verifier |
| .process_movement( |
| DeviceId(1), |
| Source::Touchscreen, |
| input_bindgen::AMOTION_EVENT_ACTION_MOVE, |
| &pointer_properties, |
| MotionFlags::empty(), |
| ) |
| .is_err()); |
| } |
| } |