diff options
-rw-r--r-- | include/android/input.h | 2 | ||||
-rw-r--r-- | include/input/InputVerifier.h | 5 | ||||
-rw-r--r-- | libs/input/Android.bp | 7 | ||||
-rw-r--r-- | libs/input/InputTransport.cpp | 6 | ||||
-rw-r--r-- | libs/input/InputVerifier.cpp | 9 | ||||
-rw-r--r-- | libs/input/rust/input.rs | 22 | ||||
-rw-r--r-- | libs/input/rust/input_verifier.rs | 641 | ||||
-rw-r--r-- | libs/input/rust/lib.rs | 26 | ||||
-rw-r--r-- | libs/input/tests/InputVerifier_test.cpp | 4 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 10 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/CursorInputMapper.h | 1 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 70 | ||||
-rw-r--r-- | services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp | 4 |
13 files changed, 776 insertions, 31 deletions
diff --git a/include/android/input.h b/include/android/input.h index ee98d7aee9..5f445509fa 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -849,6 +849,7 @@ enum { * Refer to the documentation on the MotionEvent class for descriptions of each button. */ enum { + // LINT.IfChange(AMOTION_EVENT_BUTTON) /** primary */ AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0, /** secondary */ @@ -861,6 +862,7 @@ enum { AMOTION_EVENT_BUTTON_FORWARD = 1 << 4, AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5, AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6, + // LINT.ThenChange(/frameworks/native/libs/input/rust/input.rs) }; /** diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h index 14dd463425..7d3fb469c6 100644 --- a/include/input/InputVerifier.h +++ b/include/input/InputVerifier.h @@ -47,9 +47,10 @@ public: InputVerifier(const std::string& name); android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action, - uint32_t pointerCount, + int32_t actionButton, uint32_t pointerCount, const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, int32_t flags); + const PointerCoords* pointerCoords, int32_t flags, + int32_t buttonState); void resetDevice(int32_t deviceId); diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 389fb7f6ab..d9c10ad678 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -91,6 +91,13 @@ rust_bindgen { "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN", "--allowlist-var=AMOTION_EVENT_ACTION_DOWN", "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT", + "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY", + "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY", + "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY", + "--allowlist-var=AMOTION_EVENT_BUTTON_BACK", + "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD", + "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY", + "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY", "--allowlist-var=MAX_POINTER_ID", "--allowlist-var=AINPUT_SOURCE_CLASS_NONE", "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON", diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 56ccaab9ad..d388d48e8d 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -651,9 +651,9 @@ status_t InputPublisher::publishMotionEvent( const status_t status = mChannel->sendMessage(&msg); if (status == OK && verifyEvents()) { - Result<void> result = - mInputVerifier.processMovement(deviceId, source, action, pointerCount, - pointerProperties, pointerCoords, flags); + Result<void> result = mInputVerifier.processMovement(deviceId, source, action, actionButton, + pointerCount, pointerProperties, + pointerCoords, flags, buttonState); if (!result.ok()) { LOG(ERROR) << "Bad stream: " << result.error(); return BAD_VALUE; diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp index cec244539e..e8d4c733bd 100644 --- a/libs/input/InputVerifier.cpp +++ b/libs/input/InputVerifier.cpp @@ -34,9 +34,10 @@ InputVerifier::InputVerifier(const std::string& name) : mVerifier(android::input::verifier::create(rust::String::lossy(name))){}; Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action, - uint32_t pointerCount, + int32_t actionButton, uint32_t pointerCount, const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, int32_t flags) { + const PointerCoords* pointerCoords, int32_t flags, + int32_t buttonState) { std::vector<RustPointerProperties> rpp; for (size_t i = 0; i < pointerCount; i++) { rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id}); @@ -44,7 +45,9 @@ Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, i rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()}; rust::String errorMessage = android::input::verifier::process_movement(*mVerifier, deviceId, source, action, - properties, static_cast<uint32_t>(flags)); + actionButton, properties, + static_cast<uint32_t>(flags), + static_cast<uint32_t>(buttonState)); if (errorMessage.empty()) { return {}; } else { diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index 90f509d97f..5e487ab946 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -101,6 +101,7 @@ bitflags! { /// A rust enum representation of a MotionEvent action. #[repr(u32)] +#[derive(PartialEq)] pub enum MotionAction { /// ACTION_DOWN Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN, @@ -194,6 +195,27 @@ impl MotionAction { } bitflags! { + /// MotionEvent buttons. + #[derive(Clone, Copy, Debug, Default, PartialEq)] + pub struct MotionButton: u32 { + /// Primary button (e.g. the left mouse button) + const Primary = input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY; + /// Secondary button (e.g. the right mouse button) + const Secondary = input_bindgen::AMOTION_EVENT_BUTTON_SECONDARY; + /// Tertiary button (e.g. the middle mouse button) + const Tertiary = input_bindgen::AMOTION_EVENT_BUTTON_TERTIARY; + /// Back button + const Back = input_bindgen::AMOTION_EVENT_BUTTON_BACK; + /// Forward button + const Forward = input_bindgen::AMOTION_EVENT_BUTTON_FORWARD; + /// Primary stylus button + const StylusPrimary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY; + /// Secondary stylus button + const StylusSecondary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY; + } +} + +bitflags! { /// MotionEvent flags. /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum. /// The flag values are redefined here as a bitflags API. diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs index b1d7760682..55dc56f6e3 100644 --- a/libs/input/rust/input_verifier.rs +++ b/libs/input/rust/input_verifier.rs @@ -17,13 +17,14 @@ //! Contains the InputVerifier, used to validate a stream of input events. use crate::ffi::RustPointerProperties; -use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass}; +use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass}; use log::info; use std::collections::HashMap; use std::collections::HashSet; fn verify_event( action: MotionAction, + action_button: MotionButton, pointer_properties: &[RustPointerProperties], flags: &MotionFlags, ) -> Result<(), String> { @@ -31,6 +32,14 @@ fn verify_event( if pointer_count < 1 { return Err(format!("Invalid {} event: no pointers", action)); } + if action_button != MotionButton::empty() + && action != MotionAction::ButtonPress + && action != MotionAction::ButtonRelease + { + return Err(format!( + "Invalid {action} event: has action button {action_button:?} but is not a button action" + )); + } match action { MotionAction::Down | MotionAction::HoverEnter @@ -60,17 +69,118 @@ fn verify_event( } } + MotionAction::ButtonPress | MotionAction::ButtonRelease => { + let button_count = action_button.iter().count(); + if button_count != 1 { + return Err(format!( + "Invalid {action} event: must specify a single action button, not \ + {button_count} action buttons" + )); + } + } + _ => {} } Ok(()) } +/// Keeps track of the button state for a single device and verifies events against it. +#[derive(Default)] +struct ButtonVerifier { + /// The current button state of the device. + button_state: MotionButton, + + /// The set of "pending buttons", which were seen in the last DOWN event's button state but + /// for which we haven't seen BUTTON_PRESS events yet. + /// + /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't + /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events + /// for those buttons, building up to a button state matching that of the DOWN. For example, if + /// the user presses the primary and secondary buttons at exactly the same time, we'd expect + /// this sequence: + /// + /// | Action | Action button | Button state | + /// |----------------|---------------|------------------------| + /// | `HOVER_EXIT` | - | - | + /// | `DOWN` | - | `PRIMARY`, `SECONDARY` | + /// | `BUTTON_PRESS` | `PRIMARY` | `PRIMARY` | + /// | `BUTTON_PRESS` | `SECONDARY` | `PRIMARY`, `SECONDARY` | + /// | `MOVE` | - | `PRIMARY`, `SECONDARY` | + pending_buttons: MotionButton, +} + +impl ButtonVerifier { + pub fn process_action( + &mut self, + action: MotionAction, + action_button: MotionButton, + button_state: MotionButton, + ) -> Result<(), String> { + if !self.pending_buttons.is_empty() { + // We just saw a DOWN with some additional buttons in its state, so it should be + // immediately followed by ButtonPress events for those buttons. + if action != MotionAction::ButtonPress || !self.pending_buttons.contains(action_button) + { + return Err(format!( + "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {} with \ + action button {:?}", + self.pending_buttons, action, action_button + )); + } else { + self.pending_buttons -= action_button; + } + } + let expected_state = match action { + MotionAction::Down => { + if self.button_state - button_state != MotionButton::empty() { + return Err(format!( + "DOWN event button state is missing {:?}", + self.button_state - button_state + )); + } + self.pending_buttons = button_state - self.button_state; + // We've already checked that the state isn't missing any already-down buttons, and + // extra buttons are valid on DOWN actions, so bypass the expected state check. + button_state + } + MotionAction::ButtonPress => { + if self.button_state.contains(action_button) { + return Err(format!( + "Duplicate BUTTON_PRESS; button state already contains {action_button:?}" + )); + } + self.button_state | action_button + } + MotionAction::ButtonRelease => { + if !self.button_state.contains(action_button) { + return Err(format!( + "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}" + )); + } + self.button_state - action_button + } + _ => self.button_state, + }; + if button_state != expected_state { + return Err(format!( + "Expected {action} button state to be {expected_state:?}, but was {button_state:?}" + )); + } + // DOWN events can have pending buttons, so don't update the state for them. + if action != MotionAction::Down { + self.button_state = button_state; + } + 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>>, + button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>, } impl InputVerifier { @@ -86,18 +196,22 @@ impl InputVerifier { should_log, touching_pointer_ids_by_device: HashMap::new(), hovering_pointer_ids_by_device: HashMap::new(), + button_verifier_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. + #[allow(clippy::too_many_arguments)] pub fn process_movement( &mut self, device_id: DeviceId, source: Source, action: u32, + action_button: MotionButton, pointer_properties: &[RustPointerProperties], flags: MotionFlags, + button_state: MotionButton, ) -> Result<(), String> { if !source.is_from_class(SourceClass::Pointer) { // Skip non-pointer sources like MOUSE_RELATIVE for now @@ -114,7 +228,13 @@ impl InputVerifier { ); } - verify_event(action.into(), pointer_properties, &flags)?; + verify_event(action.into(), action_button, pointer_properties, &flags)?; + + self.button_verifier_by_device.entry(device_id).or_default().process_action( + action.into(), + action_button, + button_state, + )?; match action.into() { MotionAction::Down => { @@ -288,6 +408,7 @@ impl InputVerifier { #[cfg(test)] mod tests { + use crate::input::MotionButton; use crate::input_verifier::InputVerifier; use crate::DeviceId; use crate::MotionFlags; @@ -307,8 +428,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_err()); } @@ -322,8 +445,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -331,8 +456,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -340,8 +467,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_UP, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); } @@ -355,8 +484,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); // POINTER 1 DOWN @@ -368,8 +499,10 @@ mod tests { Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + MotionButton::empty(), &two_pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); // POINTER 0 UP @@ -379,8 +512,10 @@ mod tests { Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + MotionButton::empty(), &two_pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); // ACTION_UP for pointer id=1 @@ -390,8 +525,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_UP, + MotionButton::empty(), &pointer_1_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); } @@ -405,8 +542,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -414,8 +553,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -423,8 +564,10 @@ mod tests { DeviceId(2), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -432,8 +575,10 @@ mod tests { DeviceId(2), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -441,8 +586,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_UP, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); } @@ -456,8 +603,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -465,8 +614,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_CANCEL, + MotionButton::empty(), &pointer_properties, MotionFlags::CANCELED, + MotionButton::empty(), ) .is_ok()); } @@ -480,8 +631,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); assert!(verifier @@ -489,8 +642,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_CANCEL, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), // forgot to set FLAG_CANCELED + MotionButton::empty(), ) .is_err()); } @@ -504,8 +659,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_UP, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_err()); } @@ -519,8 +676,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); @@ -529,8 +688,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); @@ -539,8 +700,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); @@ -549,8 +712,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); } @@ -564,8 +729,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); @@ -574,8 +741,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_err()); } @@ -591,8 +760,10 @@ mod tests { DeviceId(2), Source::MouseRelative, input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); } @@ -607,8 +778,10 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); // POINTER 1 DOWN @@ -620,8 +793,10 @@ mod tests { Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + MotionButton::empty(), &two_pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_ok()); // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected @@ -630,8 +805,470 @@ mod tests { DeviceId(1), Source::Touchscreen, input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn correct_button_press() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + } + + #[test] + fn button_press_without_action_button() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn button_press_with_multiple_action_buttons() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back | MotionButton::Forward, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back | MotionButton::Forward, + ) + .is_err()); + } + + #[test] + fn button_press_without_action_button_in_state() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn button_release_with_action_button_in_state() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_err()); + } + + #[test] + fn nonbutton_action_with_action_button() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn nonbutton_action_with_action_button_and_state() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_err()); + } + + #[test] + fn nonbutton_action_with_button_state_change() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_err()); + } + + #[test] + fn nonbutton_action_missing_button_state() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn up_without_button_release() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should. + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_UP, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn button_press_for_already_pressed_button() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_err()); + } + + #[test] + fn button_release_for_unpressed_button() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::empty(), + ) + .is_err()); + } + + #[test] + fn correct_multiple_button_presses_without_down() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Forward, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back | MotionButton::Forward, + ) + .is_ok()); + } + + #[test] + fn correct_down_with_button_press() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary | MotionButton::Secondary, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Secondary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary | MotionButton::Secondary, + ) + .is_ok()); + // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not + // enough BUTTON_PRESSes were sent. + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary | MotionButton::Secondary, + ) + .is_ok()); + } + + #[test] + fn down_with_button_state_change_not_followed_by_button_press() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS. + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_err()); + } + + #[test] + fn down_with_button_state_change_not_followed_by_enough_button_presses() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary | MotionButton::Secondary, + ) + .is_ok()); + // The DOWN event itself is OK, but it needs to be immediately followed by two + // BUTTON_PRESSes, one for each button. + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Primary, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_MOVE, + MotionButton::empty(), + &pointer_properties, + MotionFlags::empty(), + MotionButton::Primary, + ) + .is_err()); + } + + #[test] + fn down_missing_already_pressed_button() { + let mut verifier = InputVerifier::new("Test", /*should_log*/ false); + let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS, + MotionButton::Back, + &pointer_properties, + MotionFlags::empty(), + MotionButton::Back, + ) + .is_ok()); + assert!(verifier + .process_movement( + DeviceId(1), + Source::Mouse, + input_bindgen::AMOTION_EVENT_ACTION_DOWN, + MotionButton::empty(), &pointer_properties, MotionFlags::empty(), + MotionButton::empty(), ) .is_err()); } diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs index 4f4ea8568b..1d5793bcb3 100644 --- a/libs/input/rust/lib.rs +++ b/libs/input/rust/lib.rs @@ -24,8 +24,8 @@ mod keyboard_classifier; pub use data_store::{DataStore, DefaultFileReaderWriter}; pub use input::{ - DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionFlags, - Source, + DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionButton, + MotionFlags, Source, }; pub use input_verifier::InputVerifier; pub use keyboard_classifier::KeyboardClassifier; @@ -58,13 +58,16 @@ mod ffi { type InputVerifier; #[cxx_name = create] fn create_input_verifier(name: String) -> Box<InputVerifier>; + #[allow(clippy::too_many_arguments)] fn process_movement( verifier: &mut InputVerifier, device_id: i32, source: u32, action: u32, + action_button: u32, pointer_properties: &[RustPointerProperties], flags: u32, + button_state: u32, ) -> String; fn reset_device(verifier: &mut InputVerifier, device_id: i32); } @@ -119,13 +122,16 @@ fn create_input_verifier(name: String) -> Box<InputVerifier> { Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"))) } +#[allow(clippy::too_many_arguments)] fn process_movement( verifier: &mut InputVerifier, device_id: i32, source: u32, action: u32, + action_button: u32, pointer_properties: &[RustPointerProperties], flags: u32, + button_state: u32, ) -> String { let motion_flags = MotionFlags::from_bits(flags); if motion_flags.is_none() { @@ -135,12 +141,28 @@ fn process_movement( flags ); } + let motion_action_button = MotionButton::from_bits(action_button); + if motion_action_button.is_none() { + panic!( + "The conversion of action button 0x{action_button:08x} failed, please check if some \ + buttons need to be added to MotionButton." + ); + } + let motion_button_state = MotionButton::from_bits(button_state); + if motion_button_state.is_none() { + panic!( + "The conversion of button state 0x{button_state:08x} failed, please check if some \ + buttons need to be added to MotionButton." + ); + } let result = verifier.process_movement( DeviceId(device_id), Source::from_bits(source).unwrap(), action, + motion_action_button.unwrap(), pointer_properties, motion_flags.unwrap(), + motion_button_state.unwrap(), ); match result { Ok(()) => "".to_string(), diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp index 5bb1d56040..8e0d9068c1 100644 --- a/libs/input/tests/InputVerifier_test.cpp +++ b/libs/input/tests/InputVerifier_test.cpp @@ -49,9 +49,9 @@ TEST(InputVerifierTest, ProcessSourceClassPointer) { const Result<void> result = verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER, - AMOTION_EVENT_ACTION_DOWN, + AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*pointerCount=*/properties.size(), properties.data(), - coords.data(), /*flags=*/0); + coords.data(), /*flags=*/0, /*buttonState=*/0); ASSERT_RESULT_OK(result); } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4c4182d4c0..a8cd7f9e25 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4541,8 +4541,9 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { args.displayId.toString().c_str())); Result<void> result = it->second.processMovement(args.deviceId, args.source, args.action, - args.getPointerCount(), args.pointerProperties.data(), - args.pointerCoords.data(), args.flags); + args.actionButton, args.getPointerCount(), + args.pointerProperties.data(), args.pointerCoords.data(), + args.flags, args.buttonState); if (!result.ok()) { LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump(); } @@ -4743,9 +4744,10 @@ bool InputDispatcher::shouldRejectInjectedMotionLocked(const MotionEvent& motion Result<void> result = verifier.processMovement(deviceId, motionEvent.getSource(), motionEvent.getAction(), - motionEvent.getPointerCount(), + motionEvent.getActionButton(), motionEvent.getPointerCount(), motionEvent.getPointerProperties(), - motionEvent.getSamplePointerCoords(), flags); + motionEvent.getSamplePointerCoords(), flags, + motionEvent.getButtonState()); if (!result.ok()) { logDispatchStateLocked(); LOG(ERROR) << "Inconsistent event: " << motionEvent << ", reason: " << result.error(); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 83199227b1..773619c865 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -115,6 +115,7 @@ private: ui::Rotation mOrientation{ui::ROTATION_0}; FloatRect mBoundsInLogicalDisplay{}; + // The button state as of the last sync. int32_t mButtonState; nsecs_t mDownTime; nsecs_t mLastEventTime; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b6e27a87f8..40368114a2 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -12371,43 +12371,69 @@ protected: } void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) { + bool consumeButtonPress = false; switch (fromSource) { - case AINPUT_SOURCE_TOUCHSCREEN: + case AINPUT_SOURCE_TOUCHSCREEN: { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; break; - case AINPUT_SOURCE_STYLUS: + } + case AINPUT_SOURCE_STYLUS: { + PointerBuilder pointer = PointerBuilder(0, ToolType::STYLUS).x(50).y(50); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) .buttonState( AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) - .pointer(PointerBuilder(0, ToolType::STYLUS) - .x(50) - .y(50)) + .pointer(pointer) + .build())); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, + AINPUT_SOURCE_STYLUS) + .actionButton( + AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) + .buttonState( + AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) + .pointer(pointer) .build())); + consumeButtonPress = true; break; - case AINPUT_SOURCE_MOUSE: + } + case AINPUT_SOURCE_MOUSE: { + PointerBuilder pointer = + PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(MOUSE_POINTER_ID, - ToolType::MOUSE) - .x(50) - .y(50)) + .pointer(pointer) .build())); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, + AINPUT_SOURCE_MOUSE) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(pointer) + .build())); + consumeButtonPress = true; break; - default: + } + default: { FAIL() << "Source " << fromSource << " doesn't support drag and drop"; + } } // Window should receive motion event. mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + if (consumeButtonPress) { + mWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + } // Spy window should also receive motion event mSpyWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } @@ -12607,6 +12633,16 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { // Move to another window and release button, expect to drop item. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE, + AINPUT_SOURCE_STYLUS) + .actionButton(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) + .buttonState(0) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) + .build())) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + mDragWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) .buttonState(0) .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) @@ -12848,6 +12884,18 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { // drop to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE, + AINPUT_SOURCE_MOUSE) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .buttonState(0) + .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE) + .x(150) + .y(50)) + .build())) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + mDragWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE) diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp index 31db2fedc7..abce931eff 100644 --- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp @@ -48,9 +48,9 @@ public: auto [it, _] = mVerifiers.emplace(args.displayId, "Fuzz Verifier"); InputVerifier& verifier = it->second; const Result<void> result = - verifier.processMovement(args.deviceId, args.source, args.action, + verifier.processMovement(args.deviceId, args.source, args.action, args.actionButton, args.getPointerCount(), args.pointerProperties.data(), - args.pointerCoords.data(), args.flags); + args.pointerCoords.data(), args.flags, args.buttonState); if (result.ok()) { return args; } |