InputVerifier: verify button events and states

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.

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: 372571823
Flag: com.android.input.flags.enable_inbound_event_verification
Change-Id: I59b886bfb632f0f26ee58c40f82f447f5ea59b41
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 389fb7f..d9c10ad 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -91,6 +91,13 @@
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_BACK",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY",
         "--allowlist-var=MAX_POINTER_ID",
         "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
         "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 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..e8d4c73 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -34,9 +34,10 @@
       : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
 
 Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
-                                            uint32_t pointerCount,
+                                            int32_t actionButton, uint32_t pointerCount,
                                             const PointerProperties* pointerProperties,
-                                            const PointerCoords* pointerCoords, int32_t flags) {
+                                            const PointerCoords* pointerCoords, int32_t flags,
+                                            int32_t buttonState) {
     std::vector<RustPointerProperties> rpp;
     for (size_t i = 0; i < pointerCount; i++) {
         rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
@@ -44,7 +45,9 @@
     rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
     rust::String errorMessage =
             android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
-                                                       properties, static_cast<uint32_t>(flags));
+                                                       actionButton, properties,
+                                                       static_cast<uint32_t>(flags),
+                                                       static_cast<uint32_t>(buttonState));
     if (errorMessage.empty()) {
         return {};
     } else {
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 90f509d..5e487ab 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(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, 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 b1d7760..55dc56f 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,13 +17,14 @@
 //! Contains the InputVerifier, used to validate a stream of input events.
 
 use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass};
 use log::info;
 use std::collections::HashMap;
 use std::collections::HashSet;
 
 fn verify_event(
     action: MotionAction,
+    action_button: MotionButton,
     pointer_properties: &[RustPointerProperties],
     flags: &MotionFlags,
 ) -> Result<(), String> {
@@ -31,6 +32,14 @@
     if pointer_count < 1 {
         return Err(format!("Invalid {} event: no pointers", action));
     }
+    if action_button != MotionButton::empty()
+        && action != MotionAction::ButtonPress
+        && action != MotionAction::ButtonRelease
+    {
+        return Err(format!(
+            "Invalid {action} event: has action button {action_button:?} but is not a button action"
+        ));
+    }
     match action {
         MotionAction::Down
         | MotionAction::HoverEnter
@@ -60,17 +69,118 @@
             }
         }
 
+        MotionAction::ButtonPress | MotionAction::ButtonRelease => {
+            let button_count = action_button.iter().count();
+            if button_count != 1 {
+                return Err(format!(
+                    "Invalid {action} event: must specify a single action button, not \
+                     {button_count} action buttons"
+                ));
+            }
+        }
+
         _ => {}
     }
     Ok(())
 }
 
+/// Keeps track of the button state for a single device and verifies events against it.
+#[derive(Default)]
+struct ButtonVerifier {
+    /// The current button state of the device.
+    button_state: MotionButton,
+
+    /// The set of "pending buttons", which were seen in the last DOWN event's button state but
+    /// for which we haven't seen BUTTON_PRESS events yet.
+    ///
+    /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
+    /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
+    /// for those buttons, building up to a button state matching that of the DOWN. For example, if
+    /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
+    /// this sequence:
+    ///
+    /// | Action         | Action button | Button state           |
+    /// |----------------|---------------|------------------------|
+    /// | `HOVER_EXIT`   | -             | -                      |
+    /// | `DOWN`         | -             | `PRIMARY`, `SECONDARY` |
+    /// | `BUTTON_PRESS` | `PRIMARY`     | `PRIMARY`              |
+    /// | `BUTTON_PRESS` | `SECONDARY`   | `PRIMARY`, `SECONDARY` |
+    /// | `MOVE`         | -             | `PRIMARY`, `SECONDARY` |
+    pending_buttons: MotionButton,
+}
+
+impl ButtonVerifier {
+    pub fn process_action(
+        &mut self,
+        action: MotionAction,
+        action_button: MotionButton,
+        button_state: MotionButton,
+    ) -> Result<(), String> {
+        if !self.pending_buttons.is_empty() {
+            // We just saw a DOWN with some additional buttons in its state, so it should be
+            // immediately followed by ButtonPress events for those buttons.
+            if action != MotionAction::ButtonPress || !self.pending_buttons.contains(action_button)
+            {
+                return Err(format!(
+                    "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {} with \
+                     action button {:?}",
+                    self.pending_buttons, action, action_button
+                ));
+            } else {
+                self.pending_buttons -= action_button;
+            }
+        }
+        let expected_state = match action {
+            MotionAction::Down => {
+                if self.button_state - button_state != MotionButton::empty() {
+                    return Err(format!(
+                        "DOWN event button state is missing {:?}",
+                        self.button_state - button_state
+                    ));
+                }
+                self.pending_buttons = button_state - self.button_state;
+                // We've already checked that the state isn't missing any already-down buttons, and
+                // extra buttons are valid on DOWN actions, so bypass the expected state check.
+                button_state
+            }
+            MotionAction::ButtonPress => {
+                if self.button_state.contains(action_button) {
+                    return Err(format!(
+                        "Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
+                    ));
+                }
+                self.button_state | action_button
+            }
+            MotionAction::ButtonRelease => {
+                if !self.button_state.contains(action_button) {
+                    return Err(format!(
+                        "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}"
+                    ));
+                }
+                self.button_state - action_button
+            }
+            _ => self.button_state,
+        };
+        if button_state != expected_state {
+            return Err(format!(
+                "Expected {action} button state to be {expected_state:?}, but was {button_state:?}"
+            ));
+        }
+        // DOWN events can have pending buttons, so don't update the state for them.
+        if action != MotionAction::Down {
+            self.button_state = button_state;
+        }
+        Ok(())
+    }
+}
+
 /// The InputVerifier is used to validate a stream of input events.
 pub struct InputVerifier {
     name: String,
     should_log: bool,
     touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
     hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+    button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>,
 }
 
 impl InputVerifier {
@@ -86,18 +196,22 @@
             should_log,
             touching_pointer_ids_by_device: HashMap::new(),
             hovering_pointer_ids_by_device: HashMap::new(),
+            button_verifier_by_device: HashMap::new(),
         }
     }
 
     /// Process a pointer movement event from an InputDevice.
     /// If the event is not valid, we return an error string that describes the issue.
+    #[allow(clippy::too_many_arguments)]
     pub fn process_movement(
         &mut self,
         device_id: DeviceId,
         source: Source,
         action: u32,
+        action_button: MotionButton,
         pointer_properties: &[RustPointerProperties],
         flags: MotionFlags,
+        button_state: MotionButton,
     ) -> Result<(), String> {
         if !source.is_from_class(SourceClass::Pointer) {
             // Skip non-pointer sources like MOUSE_RELATIVE for now
@@ -114,7 +228,13 @@
             );
         }
 
-        verify_event(action.into(), pointer_properties, &flags)?;
+        verify_event(action.into(), action_button, pointer_properties, &flags)?;
+
+        self.button_verifier_by_device.entry(device_id).or_default().process_action(
+            action.into(),
+            action_button,
+            button_state,
+        )?;
 
         match action.into() {
             MotionAction::Down => {
@@ -288,6 +408,7 @@
 
 #[cfg(test)]
 mod tests {
+    use crate::input::MotionButton;
     use crate::input_verifier::InputVerifier;
     use crate::DeviceId;
     use crate::MotionFlags;
@@ -307,8 +428,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
@@ -322,8 +445,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -331,8 +456,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -340,8 +467,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -355,8 +484,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -368,8 +499,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
                     | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 0 UP
@@ -379,8 +512,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
                     | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // ACTION_UP for pointer id=1
@@ -390,8 +525,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_1_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -405,8 +542,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -414,8 +553,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -423,8 +564,10 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -432,8 +575,10 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -441,8 +586,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -456,8 +603,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -465,8 +614,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::CANCELED,
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -480,8 +631,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -489,8 +642,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(), // forgot to set FLAG_CANCELED
+                MotionButton::empty(),
             )
             .is_err());
     }
@@ -504,8 +659,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
@@ -519,8 +676,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -529,8 +688,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -539,8 +700,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -549,8 +712,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -564,8 +729,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -574,8 +741,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
@@ -591,8 +760,10 @@
                 DeviceId(2),
                 Source::MouseRelative,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -607,8 +778,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -620,8 +793,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
                     | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
@@ -630,8 +805,470 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn correct_button_press() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn button_press_without_action_button() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_with_multiple_action_buttons() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back | MotionButton::Forward,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back | MotionButton::Forward,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_without_action_button_in_state() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_release_with_action_button_in_state() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_action_button() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_action_button_and_state() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_button_state_change() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_missing_button_state() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn up_without_button_release() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_for_already_pressed_button() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_release_for_unpressed_button() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn correct_multiple_button_presses_without_down() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Forward,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back | MotionButton::Forward,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn correct_down_with_button_press() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Secondary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
+        // enough BUTTON_PRESSes were sent.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn down_with_button_state_change_not_followed_by_button_press() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn down_with_button_state_change_not_followed_by_enough_button_presses() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        // The DOWN event itself is OK, but it needs to be immediately followed by two
+        // BUTTON_PRESSes, one for each button.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn down_missing_already_pressed_button() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 4f4ea85..1d5793b 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;
@@ -58,13 +58,16 @@
         type InputVerifier;
         #[cxx_name = create]
         fn create_input_verifier(name: String) -> Box<InputVerifier>;
+        #[allow(clippy::too_many_arguments)]
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
             source: u32,
             action: u32,
+            action_button: u32,
             pointer_properties: &[RustPointerProperties],
             flags: u32,
+            button_state: u32,
         ) -> String;
         fn reset_device(verifier: &mut InputVerifier, device_id: i32);
     }
@@ -119,13 +122,16 @@
     Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
 }
 
+#[allow(clippy::too_many_arguments)]
 fn process_movement(
     verifier: &mut InputVerifier,
     device_id: i32,
     source: u32,
     action: u32,
+    action_button: u32,
     pointer_properties: &[RustPointerProperties],
     flags: u32,
+    button_state: u32,
 ) -> String {
     let motion_flags = MotionFlags::from_bits(flags);
     if motion_flags.is_none() {
@@ -135,12 +141,28 @@
             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 4c4182d..a8cd7f9 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4541,8 +4541,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();
         }
@@ -4743,9 +4744,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 8319922..773619c 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/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b6e27a8..4036811 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -12371,43 +12371,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);
     }
@@ -12607,6 +12633,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))
@@ -12848,6 +12884,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;
         }