InputVerifier: verify button events and states (attempt 2)
Check the following things:
* BUTTON_PRESS and _RELEASE actions have a single, valid action button
* No redundant BUTTON_PRESS or _RELEASE actions (i.e. for buttons that
are already pressed or released)
* Button state remains consistent throughout the stream, i.e.:
* Buttons are only added to the state by BUTTON_PRESS (though DOWN
events can have "pending" buttons on them)
* Buttons are only removed from the state by BUTTON_RELEASE
* When a DOWN event has pending buttons in its state, it is
immediately followed by a BUTTON_PRESS for each one
We could also verify that press and release events for primary,
secondary, and tertiary buttons are accompanied by down and up events.
However, I couldn't find any documentation that states which buttons
should result in down or up events, so I haven't implemented this for
now.
This is the second attempt to land this change, due to the original
causing test failures. Change I5c259c13d433d3010d2cf9c6fe01e08ba5990ef7
fixes the failures. v2 also adds a separate flag for button state
verification, as it is actually used in production to check events
injected by assistive technologies, whether or not the flag that I
previously thought was guarding it is enabled.
Test: connect a mouse and a touchpad, enable the verifier, play around
with the buttons, and check that any issues found by the verifier
appear to be legitimate. (I found b/391298464 , and checked that
the verifier caught a button problem with a partial fix to
b/372571823 .)
Test: atest --host libinput_rust_tests
Test: atest --host frameworks/native/libs/input/tests/InputVerifier_test.cpp
Test: atest --host \
frameworks/native/services/inputflinger/tests/InputDispatcher_test.cpp
Bug: 392870542
Flag: com.android.input.flags.enable_button_state_verification
Change-Id: I46f489b26df8785563e41e58135b6b5de4ff62a2
diff --git a/include/android/input.h b/include/android/input.h
index ee98d7a..5f44550 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -849,6 +849,7 @@
* 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 @@
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 14dd463..7d3fb46 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -47,9 +47,10 @@
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 cd6fe90..d2e4320 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -133,6 +133,13 @@
"--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
"--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
"--allowlist-var=AMOTION_EVENT_ACTION_UP",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_BACK",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY",
"--allowlist-var=MAX_POINTER_ID",
"--verbose",
],
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 56ccaab..d388d48 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -651,9 +651,9 @@
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 cec2445..7811ace 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "InputVerifier"
#include <android-base/logging.h>
+#include <com_android_input_flags.h>
#include <input/InputVerifier.h>
#include "input_cxx_bridge.rs.h"
@@ -26,17 +27,23 @@
using DeviceId = int32_t;
+namespace input_flags = com::android::input::flags;
+
namespace android {
// --- InputVerifier ---
InputVerifier::InputVerifier(const std::string& name)
- : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
+ : mVerifier(
+ android::input::verifier::create(rust::String::lossy(name),
+ input_flags::enable_button_state_verification())) {
+}
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 +51,9 @@
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/input_flags.aconfig b/libs/input/input_flags.aconfig
index 72a6fdf..4e187ed 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,6 +16,16 @@
}
flag {
+ name: "enable_button_state_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+ bug: "392870542"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "remove_input_channel_from_windowstate"
namespace: "input"
description: "Do not store a copy of input channel inside WindowState."
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 6956a84..6eb2d73 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -101,6 +101,7 @@
/// A rust enum representation of a MotionEvent action.
#[repr(u32)]
+#[derive(Eq, PartialEq)]
pub enum MotionAction {
/// ACTION_DOWN
Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
@@ -194,6 +195,27 @@
}
bitflags! {
+ /// MotionEvent buttons.
+ #[derive(Clone, Copy, Debug, Default, Eq, 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 bddd2a7..6d94316 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,20 +17,30 @@
//! 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,
+ verify_buttons: bool,
) -> Result<(), String> {
let pointer_count = pointer_properties.len();
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
@@ -58,22 +68,126 @@
}
}
+ MotionAction::ButtonPress | MotionAction::ButtonRelease => {
+ if verify_buttons {
+ 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,
+ verify_buttons: 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 {
/// Create a new InputVerifier.
- pub fn new(name: &str, should_log: bool) -> Self {
+ pub fn new(name: &str, should_log: bool, verify_buttons: bool) -> Self {
logger::init(
logger::Config::default()
.with_tag_on_device("InputVerifier")
@@ -82,20 +196,25 @@
Self {
name: name.to_owned(),
should_log,
+ verify_buttons,
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
@@ -112,7 +231,21 @@
);
}
- verify_event(action.into(), pointer_properties, &flags)?;
+ verify_event(
+ action.into(),
+ action_button,
+ pointer_properties,
+ &flags,
+ self.verify_buttons,
+ )?;
+
+ if self.verify_buttons {
+ self.button_verifier_by_device.entry(device_id).or_default().process_action(
+ action.into(),
+ action_button,
+ button_state,
+ )?;
+ }
match action.into() {
MotionAction::Down => {
@@ -286,6 +419,7 @@
#[cfg(test)]
mod tests {
+ use crate::input::MotionButton;
use crate::input_verifier::InputVerifier;
use crate::DeviceId;
use crate::MotionFlags;
@@ -297,7 +431,8 @@
* 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 mut verifier =
+ InputVerifier::new("Test", /*should_log*/ true, /*verify_buttons*/ true);
let pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
@@ -305,23 +440,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_err());
}
#[test]
fn single_pointer_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -329,8 +469,10 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -338,23 +480,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
}
#[test]
fn two_pointer_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
// POINTER 1 DOWN
@@ -366,8 +513,10 @@
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
@@ -377,8 +526,10 @@
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
@@ -388,23 +539,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
+ MotionButton::empty(),
&pointer_1_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
}
#[test]
fn multi_device_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -412,8 +568,10 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -421,8 +579,10 @@
DeviceId(2),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -430,8 +590,10 @@
DeviceId(2),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -439,23 +601,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
}
#[test]
fn action_cancel() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -463,23 +630,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::CANCELED,
+ MotionButton::empty(),
)
.is_ok());
}
#[test]
fn invalid_action_cancel() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
assert!(verifier
@@ -487,38 +659,46 @@
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());
}
#[test]
fn invalid_up() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_err());
}
#[test]
fn correct_hover_sequence() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
@@ -527,8 +707,10 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
@@ -537,8 +719,10 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
@@ -547,23 +731,28 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
}
#[test]
fn double_hover_enter() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
@@ -572,8 +761,10 @@
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_err());
}
@@ -582,15 +773,18 @@
// 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 mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(2),
Source::MouseRelative,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
}
@@ -598,15 +792,18 @@
// 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 mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
.process_movement(
DeviceId(1),
Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ MotionButton::empty(),
&pointer_properties,
MotionFlags::empty(),
+ MotionButton::empty(),
)
.is_ok());
// POINTER 1 DOWN
@@ -618,8 +815,10 @@
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
@@ -628,8 +827,487 @@
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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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, /*verify_buttons*/ true);
+ 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 4f4ea85..6db4356 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -24,8 +24,8 @@
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;
@@ -57,14 +57,17 @@
/// ```
type InputVerifier;
#[cxx_name = create]
- fn create_input_verifier(name: String) -> Box<InputVerifier>;
+ fn create_input_verifier(name: String, verify_buttons: bool) -> 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);
}
@@ -115,17 +118,20 @@
use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties};
-fn create_input_verifier(name: String) -> Box<InputVerifier> {
- Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+fn create_input_verifier(name: String, verify_buttons: bool) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"), verify_buttons))
}
+#[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 @@
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 5bb1d56..8e0d906 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -49,9 +49,9 @@
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 11ba592..c1ddb6a 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4550,8 +4550,9 @@
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();
}
@@ -4747,9 +4748,10 @@
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 301632f..f2b2b6f 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -115,6 +115,7 @@
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/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index fd9884b..914f5ab 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -1721,15 +1721,16 @@
mParamContinueGesture(std::get<1>(GetParam())),
mParamEndGesture(std::get<2>(GetParam())),
mDeviceContext(*mDevice, EVENTHUB_ID),
- mConverter(*mReader->getContext(), mDeviceContext, DEVICE_ID),
- mVerifier("Test verifier") {
+ mConverter(*mReader->getContext(), mDeviceContext, DEVICE_ID) {
mConverter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+ input_flags::enable_button_state_verification(true);
+ mVerifier = std::make_unique<InputVerifier>("Test verifier");
}
base::Result<void> processMotionArgs(NotifyMotionArgs arg) {
- return mVerifier.processMovement(arg.deviceId, arg.source, arg.action,
- arg.getPointerCount(), arg.pointerProperties.data(),
- arg.pointerCoords.data(), arg.flags);
+ return mVerifier->processMovement(arg.deviceId, arg.source, arg.action, arg.actionButton,
+ arg.getPointerCount(), arg.pointerProperties.data(),
+ arg.pointerCoords.data(), arg.flags, arg.buttonState);
}
void verifyArgsFromGesture(const Gesture& gesture, size_t gestureIndex) {
@@ -1755,7 +1756,7 @@
InputDeviceContext mDeviceContext;
GestureConverter mConverter;
- InputVerifier mVerifier;
+ std::unique_ptr<InputVerifier> mVerifier;
};
TEST_P(GestureConverterConsistencyTest, ButtonChangesDuringGesture) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index c0e2060..7cc4ff7 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -12405,43 +12405,69 @@
}
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);
}
@@ -12641,6 +12667,16 @@
// 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))
@@ -12882,6 +12918,18 @@
// 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 31db2fe..abce931 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -48,9 +48,9 @@
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;
}