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/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);
 }