Merge changes I65b2b48d,I0d41ba77 into main

* changes:
  [20/n Dispatcher refactor] Allow access to all TouchedWindowHandles
  [19/n Dispatcher refactor] Remove findTouchStateWindowAndDisplay
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index 6d14d56..277300d 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -772,8 +772,8 @@
             break;
 
             case 'i':
-                displayId = DisplayId::fromValue<PhysicalDisplayId>(atoll(optarg));
-                if (!displayId) {
+                displayId = PhysicalDisplayId::fromValue(atoll(optarg));
+                if (std::find(ids.begin(), ids.end(), displayId) == ids.end()) {
                     fprintf(stderr, "Invalid display ID: %s.\n", optarg);
                     exit(4);
                 }
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/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 152c815..83f4719 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -147,9 +147,11 @@
          data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
          data.writeInt32(uid);
          data.writeString16(callingPackage);
-         remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
+         status_t err = remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
          // fail on exception
-         if (reply.readExceptionCode() != 0) return false;
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return false;
+         }
          return reply.readInt32() == 1;
     }
 
@@ -159,9 +161,9 @@
         data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
         data.writeInt32(uid);
         data.writeString16(callingPackage);
-        remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
+        status_t err = remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
         // fail on exception
-        if (reply.readExceptionCode() != 0) {
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
             return ActivityManager::PROCESS_STATE_UNKNOWN;
         }
         return reply.readInt32();
@@ -192,7 +194,7 @@
         data.writeInt32(appPid);
         status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
                                           IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
@@ -207,7 +209,7 @@
         data.writeInt32(appPid);
         status_t err =
                 remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
@@ -224,7 +226,7 @@
         data.writeInt32(appPid);
         status_t err = remote()->transact(LOG_FGS_API_STATE_CHANGED_TRANSACTION, data, &reply,
                                           IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 44aac9b..ebfc62f 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -67,7 +67,6 @@
         reserved(0),
         cornerRadius(0.0f),
         clientDrawnCornerRadius(0.0f),
-        clientDrawnShadowRadius(0.0f),
         backgroundBlurRadius(0),
         color(0),
         bufferTransform(0),
@@ -143,7 +142,6 @@
     SAFE_PARCEL(output.write, colorTransform.asArray(), 16 * sizeof(float));
     SAFE_PARCEL(output.writeFloat, cornerRadius);
     SAFE_PARCEL(output.writeFloat, clientDrawnCornerRadius);
-    SAFE_PARCEL(output.writeFloat, clientDrawnShadowRadius);
     SAFE_PARCEL(output.writeUint32, backgroundBlurRadius);
     SAFE_PARCEL(output.writeParcelable, metadata);
     SAFE_PARCEL(output.writeFloat, bgColor.r);
@@ -279,7 +277,6 @@
     SAFE_PARCEL(input.read, &colorTransform, 16 * sizeof(float));
     SAFE_PARCEL(input.readFloat, &cornerRadius);
     SAFE_PARCEL(input.readFloat, &clientDrawnCornerRadius);
-    SAFE_PARCEL(input.readFloat, &clientDrawnShadowRadius);
     SAFE_PARCEL(input.readUint32, &backgroundBlurRadius);
     SAFE_PARCEL(input.readParcelable, &metadata);
 
@@ -606,10 +603,6 @@
         what |= eClientDrawnCornerRadiusChanged;
         clientDrawnCornerRadius = other.clientDrawnCornerRadius;
     }
-    if (other.what & eClientDrawnShadowsChanged) {
-        what |= eClientDrawnShadowsChanged;
-        clientDrawnShadowRadius = other.clientDrawnShadowRadius;
-    }
     if (other.what & eBackgroundBlurRadiusChanged) {
         what |= eBackgroundBlurRadiusChanged;
         backgroundBlurRadius = other.backgroundBlurRadius;
@@ -824,7 +817,6 @@
     CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
     CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
     CHECK_DIFF(diff, eClientDrawnCornerRadiusChanged, other, clientDrawnCornerRadius);
-    CHECK_DIFF(diff, eClientDrawnShadowsChanged, other, clientDrawnShadowRadius);
     CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
     if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
     if (other.what & eRelativeLayerChanged) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 852885b..37ed23b 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1415,9 +1415,8 @@
             ComposerServiceAIDL::getComposerService()->getPhysicalDisplayIds(&displayIds);
     if (status.isOk()) {
         physicalDisplayIds.reserve(displayIds.size());
-        for (auto item : displayIds) {
-            auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(item));
-            physicalDisplayIds.push_back(*id);
+        for (auto id : displayIds) {
+            physicalDisplayIds.push_back(PhysicalDisplayId::fromValue(static_cast<uint64_t>(id)));
         }
     }
     return physicalDisplayIds;
@@ -1688,17 +1687,6 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setClientDrawnShadowRadius(
-        const sp<SurfaceControl>& sc, float clientDrawnShadowRadius) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-    s->what |= layer_state_t::eClientDrawnShadowsChanged;
-    s->clientDrawnShadowRadius = clientDrawnShadowRadius;
-    return *this;
-}
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackgroundBlurRadius(
         const sp<SurfaceControl>& sc, int backgroundBlurRadius) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h
index 6530b50..fb4aaa7 100644
--- a/libs/gui/include/gui/InputTransferToken.h
+++ b/libs/gui/include/gui/InputTransferToken.h
@@ -25,7 +25,7 @@
 namespace android {
 struct InputTransferToken : public RefBase, Parcelable {
 public:
-    InputTransferToken() { mToken = new BBinder(); }
+    InputTransferToken() { mToken = sp<BBinder>::make(); }
 
     InputTransferToken(const sp<IBinder>& token) { mToken = token; }
 
@@ -50,4 +50,4 @@
     return token1->mToken == token2->mToken;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 1002614..d04b861 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -232,7 +232,6 @@
         ePictureProfileHandleChanged = 0x80000'00000000,
         eAppContentPriorityChanged = 0x100000'00000000,
         eClientDrawnCornerRadiusChanged = 0x200000'00000000,
-        eClientDrawnShadowsChanged = 0x400000'00000000,
     };
 
     layer_state_t();
@@ -276,7 +275,7 @@
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
             layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
-            layer_state_t::eClientDrawnShadowsChanged | layer_state_t::eStretchChanged |
+            layer_state_t::eStretchChanged |
             layer_state_t::ePictureProfileHandleChanged | layer_state_t::eAppContentPriorityChanged;
 
     // Changes which invalidates the layer's visible region in CE.
@@ -336,7 +335,6 @@
     matrix22_t matrix;
     float cornerRadius;
     float clientDrawnCornerRadius;
-    float clientDrawnShadowRadius;
     uint32_t backgroundBlurRadius;
 
     sp<SurfaceControl> relativeLayerSurfaceControl;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index d30a830..10225cc 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -569,10 +569,6 @@
         // radius is drawn by the client and not SurfaceFlinger.
         Transaction& setClientDrawnCornerRadius(const sp<SurfaceControl>& sc,
                                                 float clientDrawnCornerRadius);
-        // Sets the client drawn shadow radius for the layer. This indicates that the shadows
-        // are drawn by the client and not SurfaceFlinger.
-        Transaction& setClientDrawnShadowRadius(const sp<SurfaceControl>& sc,
-                                                float clientDrawnShadowRadius);
         Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
                                              int backgroundBlurRadius);
         Transaction& setBlurRegions(const sp<SurfaceControl>& sc,
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index cd6fe90..d2e4320 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -133,6 +133,13 @@
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
         "--allowlist-var=AMOTION_EVENT_ACTION_UP",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_BACK",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY",
+        "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY",
         "--allowlist-var=MAX_POINTER_ID",
         "--verbose",
     ],
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 56ccaab..d388d48 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -651,9 +651,9 @@
     const status_t status = mChannel->sendMessage(&msg);
 
     if (status == OK && verifyEvents()) {
-        Result<void> result =
-                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
-                                               pointerProperties, pointerCoords, flags);
+        Result<void> result = mInputVerifier.processMovement(deviceId, source, action, actionButton,
+                                                             pointerCount, pointerProperties,
+                                                             pointerCoords, flags, buttonState);
         if (!result.ok()) {
             LOG(ERROR) << "Bad stream: " << result.error();
             return BAD_VALUE;
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index cec2445..7811ace 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "InputVerifier"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
 #include <input/InputVerifier.h>
 #include "input_cxx_bridge.rs.h"
 
@@ -26,17 +27,23 @@
 
 using DeviceId = int32_t;
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 // --- InputVerifier ---
 
 InputVerifier::InputVerifier(const std::string& name)
-      : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
+      : mVerifier(
+                android::input::verifier::create(rust::String::lossy(name),
+                                                 input_flags::enable_button_state_verification())) {
+}
 
 Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
-                                            uint32_t pointerCount,
+                                            int32_t actionButton, uint32_t pointerCount,
                                             const PointerProperties* pointerProperties,
-                                            const PointerCoords* pointerCoords, int32_t flags) {
+                                            const PointerCoords* pointerCoords, int32_t flags,
+                                            int32_t buttonState) {
     std::vector<RustPointerProperties> rpp;
     for (size_t i = 0; i < pointerCount; i++) {
         rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
@@ -44,7 +51,9 @@
     rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
     rust::String errorMessage =
             android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
-                                                       properties, static_cast<uint32_t>(flags));
+                                                       actionButton, properties,
+                                                       static_cast<uint32_t>(flags),
+                                                       static_cast<uint32_t>(buttonState));
     if (errorMessage.empty()) {
         return {};
     } else {
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 72a6fdf..4e187ed 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,6 +16,16 @@
 }
 
 flag {
+  name: "enable_button_state_verification"
+  namespace: "input"
+  description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+  bug: "392870542"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "remove_input_channel_from_windowstate"
   namespace: "input"
   description: "Do not store a copy of input channel inside WindowState."
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 6956a84..6eb2d73 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -101,6 +101,7 @@
 
 /// A rust enum representation of a MotionEvent action.
 #[repr(u32)]
+#[derive(Eq, PartialEq)]
 pub enum MotionAction {
     /// ACTION_DOWN
     Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
@@ -194,6 +195,27 @@
 }
 
 bitflags! {
+    /// MotionEvent buttons.
+    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+    pub struct MotionButton: u32 {
+        /// Primary button (e.g. the left mouse button)
+        const Primary = input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY;
+        /// Secondary button (e.g. the right mouse button)
+        const Secondary = input_bindgen::AMOTION_EVENT_BUTTON_SECONDARY;
+        /// Tertiary button (e.g. the middle mouse button)
+        const Tertiary = input_bindgen::AMOTION_EVENT_BUTTON_TERTIARY;
+        /// Back button
+        const Back = input_bindgen::AMOTION_EVENT_BUTTON_BACK;
+        /// Forward button
+        const Forward = input_bindgen::AMOTION_EVENT_BUTTON_FORWARD;
+        /// Primary stylus button
+        const StylusPrimary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY;
+        /// Secondary stylus button
+        const StylusSecondary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY;
+    }
+}
+
+bitflags! {
     /// MotionEvent flags.
     /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
     /// The flag values are redefined here as a bitflags API.
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index bddd2a7..6d94316 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,20 +17,30 @@
 //! Contains the InputVerifier, used to validate a stream of input events.
 
 use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass};
 use log::info;
 use std::collections::HashMap;
 use std::collections::HashSet;
 
 fn verify_event(
     action: MotionAction,
+    action_button: MotionButton,
     pointer_properties: &[RustPointerProperties],
     flags: &MotionFlags,
+    verify_buttons: bool,
 ) -> Result<(), String> {
     let pointer_count = pointer_properties.len();
     if pointer_count < 1 {
         return Err(format!("Invalid {} event: no pointers", action));
     }
+    if action_button != MotionButton::empty()
+        && action != MotionAction::ButtonPress
+        && action != MotionAction::ButtonRelease
+    {
+        return Err(format!(
+            "Invalid {action} event: has action button {action_button:?} but is not a button action"
+        ));
+    }
     match action {
         MotionAction::Down
         | MotionAction::HoverEnter
@@ -58,22 +68,126 @@
             }
         }
 
+        MotionAction::ButtonPress | MotionAction::ButtonRelease => {
+            if verify_buttons {
+                let button_count = action_button.iter().count();
+                if button_count != 1 {
+                    return Err(format!(
+                        "Invalid {action} event: must specify a single action button, not \
+                         {button_count} action buttons"
+                    ));
+                }
+            }
+        }
+
         _ => {}
     }
     Ok(())
 }
 
+/// Keeps track of the button state for a single device and verifies events against it.
+#[derive(Default)]
+struct ButtonVerifier {
+    /// The current button state of the device.
+    button_state: MotionButton,
+
+    /// The set of "pending buttons", which were seen in the last DOWN event's button state but
+    /// for which we haven't seen BUTTON_PRESS events yet.
+    ///
+    /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
+    /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
+    /// for those buttons, building up to a button state matching that of the DOWN. For example, if
+    /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
+    /// this sequence:
+    ///
+    /// | Action         | Action button | Button state           |
+    /// |----------------|---------------|------------------------|
+    /// | `HOVER_EXIT`   | -             | -                      |
+    /// | `DOWN`         | -             | `PRIMARY`, `SECONDARY` |
+    /// | `BUTTON_PRESS` | `PRIMARY`     | `PRIMARY`              |
+    /// | `BUTTON_PRESS` | `SECONDARY`   | `PRIMARY`, `SECONDARY` |
+    /// | `MOVE`         | -             | `PRIMARY`, `SECONDARY` |
+    pending_buttons: MotionButton,
+}
+
+impl ButtonVerifier {
+    pub fn process_action(
+        &mut self,
+        action: MotionAction,
+        action_button: MotionButton,
+        button_state: MotionButton,
+    ) -> Result<(), String> {
+        if !self.pending_buttons.is_empty() {
+            // We just saw a DOWN with some additional buttons in its state, so it should be
+            // immediately followed by ButtonPress events for those buttons.
+            if action != MotionAction::ButtonPress || !self.pending_buttons.contains(action_button)
+            {
+                return Err(format!(
+                    "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {} with \
+                     action button {:?}",
+                    self.pending_buttons, action, action_button
+                ));
+            } else {
+                self.pending_buttons -= action_button;
+            }
+        }
+        let expected_state = match action {
+            MotionAction::Down => {
+                if self.button_state - button_state != MotionButton::empty() {
+                    return Err(format!(
+                        "DOWN event button state is missing {:?}",
+                        self.button_state - button_state
+                    ));
+                }
+                self.pending_buttons = button_state - self.button_state;
+                // We've already checked that the state isn't missing any already-down buttons, and
+                // extra buttons are valid on DOWN actions, so bypass the expected state check.
+                button_state
+            }
+            MotionAction::ButtonPress => {
+                if self.button_state.contains(action_button) {
+                    return Err(format!(
+                        "Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
+                    ));
+                }
+                self.button_state | action_button
+            }
+            MotionAction::ButtonRelease => {
+                if !self.button_state.contains(action_button) {
+                    return Err(format!(
+                        "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}"
+                    ));
+                }
+                self.button_state - action_button
+            }
+            _ => self.button_state,
+        };
+        if button_state != expected_state {
+            return Err(format!(
+                "Expected {action} button state to be {expected_state:?}, but was {button_state:?}"
+            ));
+        }
+        // DOWN events can have pending buttons, so don't update the state for them.
+        if action != MotionAction::Down {
+            self.button_state = button_state;
+        }
+        Ok(())
+    }
+}
+
 /// The InputVerifier is used to validate a stream of input events.
 pub struct InputVerifier {
     name: String,
     should_log: bool,
+    verify_buttons: bool,
     touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
     hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+    button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>,
 }
 
 impl InputVerifier {
     /// Create a new InputVerifier.
-    pub fn new(name: &str, should_log: bool) -> Self {
+    pub fn new(name: &str, should_log: bool, verify_buttons: bool) -> Self {
         logger::init(
             logger::Config::default()
                 .with_tag_on_device("InputVerifier")
@@ -82,20 +196,25 @@
         Self {
             name: name.to_owned(),
             should_log,
+            verify_buttons,
             touching_pointer_ids_by_device: HashMap::new(),
             hovering_pointer_ids_by_device: HashMap::new(),
+            button_verifier_by_device: HashMap::new(),
         }
     }
 
     /// Process a pointer movement event from an InputDevice.
     /// If the event is not valid, we return an error string that describes the issue.
+    #[allow(clippy::too_many_arguments)]
     pub fn process_movement(
         &mut self,
         device_id: DeviceId,
         source: Source,
         action: u32,
+        action_button: MotionButton,
         pointer_properties: &[RustPointerProperties],
         flags: MotionFlags,
+        button_state: MotionButton,
     ) -> Result<(), String> {
         if !source.is_from_class(SourceClass::Pointer) {
             // Skip non-pointer sources like MOUSE_RELATIVE for now
@@ -112,7 +231,21 @@
             );
         }
 
-        verify_event(action.into(), pointer_properties, &flags)?;
+        verify_event(
+            action.into(),
+            action_button,
+            pointer_properties,
+            &flags,
+            self.verify_buttons,
+        )?;
+
+        if self.verify_buttons {
+            self.button_verifier_by_device.entry(device_id).or_default().process_action(
+                action.into(),
+                action_button,
+                button_state,
+            )?;
+        }
 
         match action.into() {
             MotionAction::Down => {
@@ -286,6 +419,7 @@
 
 #[cfg(test)]
 mod tests {
+    use crate::input::MotionButton;
     use crate::input_verifier::InputVerifier;
     use crate::DeviceId;
     use crate::MotionFlags;
@@ -297,7 +431,8 @@
      * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
      */
     fn bad_down_event() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ true, /*verify_buttons*/ true);
         let pointer_properties =
             Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
         assert!(verifier
@@ -305,23 +440,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
 
     #[test]
     fn single_pointer_stream() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -329,8 +469,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -338,23 +480,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
 
     #[test]
     fn two_pointer_stream() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -366,8 +513,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
                     | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 0 UP
@@ -377,8 +526,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
                     | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // ACTION_UP for pointer id=1
@@ -388,23 +539,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_1_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
 
     #[test]
     fn multi_device_stream() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -412,8 +568,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -421,8 +579,10 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -430,8 +590,10 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -439,23 +601,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
 
     #[test]
     fn action_cancel() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -463,23 +630,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::CANCELED,
+                MotionButton::empty(),
             )
             .is_ok());
     }
 
     #[test]
     fn invalid_action_cancel() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -487,38 +659,46 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(), // forgot to set FLAG_CANCELED
+                MotionButton::empty(),
             )
             .is_err());
     }
 
     #[test]
     fn invalid_up() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
 
     #[test]
     fn correct_hover_sequence() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -527,8 +707,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -537,8 +719,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -547,23 +731,28 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
 
     #[test]
     fn double_hover_enter() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
 
@@ -572,8 +761,10 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
@@ -582,15 +773,18 @@
     // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
     #[test]
     fn relative_mouse_move() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(2),
                 Source::MouseRelative,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -598,15 +792,18 @@
     // Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
     #[test]
     fn move_with_wrong_number_of_pointers() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
             .process_movement(
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -618,8 +815,10 @@
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
                     | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                MotionButton::empty(),
                 &two_pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_ok());
         // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
@@ -628,8 +827,487 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn correct_button_press() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn button_press_without_action_button() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_with_multiple_action_buttons() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back | MotionButton::Forward,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back | MotionButton::Forward,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_without_action_button_in_state() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_release_with_action_button_in_state() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_action_button() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_action_button_and_state() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_with_button_state_change() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn nonbutton_action_missing_button_state() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn up_without_button_release() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_press_for_already_pressed_button() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn button_release_for_unpressed_button() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn correct_multiple_button_presses_without_down() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Forward,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back | MotionButton::Forward,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn correct_down_with_button_press() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Secondary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
+        // enough BUTTON_PRESSes were sent.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn down_with_button_state_change_not_followed_by_button_press() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn down_with_button_state_change_not_followed_by_enough_button_presses() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary | MotionButton::Secondary,
+            )
+            .is_ok());
+        // The DOWN event itself is OK, but it needs to be immediately followed by two
+        // BUTTON_PRESSes, one for each button.
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Primary,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Primary,
+            )
+            .is_err());
+    }
+
+    #[test]
+    fn down_missing_already_pressed_button() {
+        let mut verifier =
+            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                MotionButton::Back,
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::Back,
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Mouse,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                MotionButton::empty(),
+                &pointer_properties,
+                MotionFlags::empty(),
+                MotionButton::empty(),
             )
             .is_err());
     }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 4f4ea85..6db4356 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -24,8 +24,8 @@
 
 pub use data_store::{DataStore, DefaultFileReaderWriter};
 pub use input::{
-    DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionFlags,
-    Source,
+    DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionButton,
+    MotionFlags, Source,
 };
 pub use input_verifier::InputVerifier;
 pub use keyboard_classifier::KeyboardClassifier;
@@ -57,14 +57,17 @@
         /// ```
         type InputVerifier;
         #[cxx_name = create]
-        fn create_input_verifier(name: String) -> Box<InputVerifier>;
+        fn create_input_verifier(name: String, verify_buttons: bool) -> Box<InputVerifier>;
+        #[allow(clippy::too_many_arguments)]
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
             source: u32,
             action: u32,
+            action_button: u32,
             pointer_properties: &[RustPointerProperties],
             flags: u32,
+            button_state: u32,
         ) -> String;
         fn reset_device(verifier: &mut InputVerifier, device_id: i32);
     }
@@ -115,17 +118,20 @@
 
 use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties};
 
-fn create_input_verifier(name: String) -> Box<InputVerifier> {
-    Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+fn create_input_verifier(name: String, verify_buttons: bool) -> Box<InputVerifier> {
+    Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"), verify_buttons))
 }
 
+#[allow(clippy::too_many_arguments)]
 fn process_movement(
     verifier: &mut InputVerifier,
     device_id: i32,
     source: u32,
     action: u32,
+    action_button: u32,
     pointer_properties: &[RustPointerProperties],
     flags: u32,
+    button_state: u32,
 ) -> String {
     let motion_flags = MotionFlags::from_bits(flags);
     if motion_flags.is_none() {
@@ -135,12 +141,28 @@
             flags
         );
     }
+    let motion_action_button = MotionButton::from_bits(action_button);
+    if motion_action_button.is_none() {
+        panic!(
+            "The conversion of action button 0x{action_button:08x} failed, please check if some \
+             buttons need to be added to MotionButton."
+        );
+    }
+    let motion_button_state = MotionButton::from_bits(button_state);
+    if motion_button_state.is_none() {
+        panic!(
+            "The conversion of button state 0x{button_state:08x} failed, please check if some \
+             buttons need to be added to MotionButton."
+        );
+    }
     let result = verifier.process_movement(
         DeviceId(device_id),
         Source::from_bits(source).unwrap(),
         action,
+        motion_action_button.unwrap(),
         pointer_properties,
         motion_flags.unwrap(),
+        motion_button_state.unwrap(),
     );
     match result {
         Ok(()) => "".to_string(),
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
index 5bb1d56..8e0d906 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -49,9 +49,9 @@
 
     const Result<void> result =
             verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER,
-                                     AMOTION_EVENT_ACTION_DOWN,
+                                     AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
                                      /*pointerCount=*/properties.size(), properties.data(),
-                                     coords.data(), /*flags=*/0);
+                                     coords.data(), /*flags=*/0, /*buttonState=*/0);
     ASSERT_RESULT_OK(result);
 }
 
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index 897f5cf..ff96b08 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -39,12 +39,36 @@
 namespace skia {
 
 KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
-    // A shader to sample each vertex of a unit regular heptagon
-    // plus the original fragment coordinate.
-    SkString blurString(R"(
+    // A shader to sample each vertex of a square, plus the original fragment coordinate,
+    // using a total of 5 samples.
+    SkString lowSampleBlurString(R"(
         uniform shader child;
         uniform float in_blurOffset;
         uniform float in_crossFade;
+        uniform float in_weightedCrossFade;
+
+        const float2 STEP_0 = float2( 0.707106781, 0.707106781);
+        const float2 STEP_1 = float2( 0.707106781, -0.707106781);
+        const float2 STEP_2 = float2(-0.707106781, -0.707106781);
+        const float2 STEP_3 = float2(-0.707106781, 0.707106781);
+
+        half4 main(float2 xy) {
+            half3 c = child.eval(xy).rgb;
+
+            c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
+
+            return half4(c * in_weightedCrossFade, in_crossFade);
+        }
+    )");
+
+    // A shader to sample each vertex of a unit regular heptagon, plus the original fragment
+    // coordinate, using a total of 8 samples.
+    SkString highSampleBlurString(R"(
+        uniform shader child;
+        uniform float in_blurOffset;
 
         const float2 STEP_0 = float2( 1.0, 0.0);
         const float2 STEP_1 = float2( 0.623489802,  0.781831482);
@@ -65,39 +89,46 @@
             c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
             c += child.eval(xy + STEP_6 * in_blurOffset).rgb;
 
-            return half4(c * 0.125 * in_crossFade, in_crossFade);
+            return half4(c * 0.125, 1.0);
         }
     )");
 
-    auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
-    LOG_ALWAYS_FATAL_IF(!blurEffect, "RuntimeShader error: %s", error.c_str());
-    mBlurEffect = std::move(blurEffect);
+    auto [lowSampleBlurEffect, error] = SkRuntimeEffect::MakeForShader(lowSampleBlurString);
+    auto [highSampleBlurEffect, error2] = SkRuntimeEffect::MakeForShader(highSampleBlurString);
+    LOG_ALWAYS_FATAL_IF(!lowSampleBlurEffect, "RuntimeShader error: %s", error.c_str());
+    LOG_ALWAYS_FATAL_IF(!highSampleBlurEffect, "RuntimeShader error: %s", error2.c_str());
+    mLowSampleBlurEffect = std::move(lowSampleBlurEffect);
+    mHighSampleBlurEffect = std::move(highSampleBlurEffect);
 }
 
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface,
                                     const sk_sp<SkImage>& readImage, const float radius,
-                                    const float alpha) const {
+                                    const float alpha,
+                                    const sk_sp<SkRuntimeEffect>& blurEffect) const {
     const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
     SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
     blurInto(drawSurface,
              readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                    SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                    blurMatrix),
-             readImage->width() / static_cast<float>(drawSurface->width()), radius, alpha);
+             radius, alpha, blurEffect);
 }
 
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
-                                    const float inverseScale, const float radius,
-                                    const float alpha) const {
+                                    const float radius, const float alpha,
+                                    const sk_sp<SkRuntimeEffect>& blurEffect) const {
     SkPaint paint;
     if (radius == 0) {
         paint.setShader(std::move(input));
         paint.setAlphaf(alpha);
     } else {
-        SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+        SkRuntimeShaderBuilder blurBuilder(blurEffect);
         blurBuilder.child("child") = std::move(input);
+        if (blurEffect == mLowSampleBlurEffect) {
+            blurBuilder.uniform("in_crossFade") = alpha;
+            blurBuilder.uniform("in_weightedCrossFade") = alpha * 0.2f;
+        }
         blurBuilder.uniform("in_blurOffset") = radius;
-        blurBuilder.uniform("in_crossFade") = alpha;
         paint.setShader(blurBuilder.makeShader(nullptr));
     }
     paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
@@ -163,16 +194,19 @@
                 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                   SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                   blurMatrix);
-        blurInto(surfaces[0], std::move(sourceShader), kInputScale, kWeights[0] * step, 1.0f);
+        blurInto(surfaces[0], std::move(sourceShader), kWeights[0] * step, 1.0f,
+                 mLowSampleBlurEffect);
     }
     // Next the remaining downscale blur passes.
     for (int i = 0; i < filterPasses; i++) {
-        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f);
+        // Blur with the higher sample effect into the smaller buffers, for better visual quality.
+        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f,
+                 i == 0 ? mLowSampleBlurEffect : mHighSampleBlurEffect);
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
         blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), kWeights[4 - i] * step,
-                 std::min(1.0f, filterDepth - i));
+                 std::min(1.0f, filterDepth - i), mLowSampleBlurEffect);
     }
     return surfaces[0]->makeTemporaryImage();
 }
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
index 6f4adbf..5efda35 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
@@ -41,13 +41,14 @@
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
 
 private:
-    sk_sp<SkRuntimeEffect> mBlurEffect;
+    sk_sp<SkRuntimeEffect> mLowSampleBlurEffect;
+    sk_sp<SkRuntimeEffect> mHighSampleBlurEffect;
 
     void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkImage>& readImage,
-                  const float radius, const float alpha) const;
+                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
 
     void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkShader> input,
-                  const float inverseScale, const float radius, const float alpha) const;
+                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
 };
 
 } // namespace skia
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index b7ef9b3..78e84fc 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -444,7 +444,7 @@
             (edid.hashedBlockZeroSerialNumberOpt.value_or(0) >> 11) ^
             (edid.hashedDescriptorBlockSerialNumberOpt.value_or(0) << 23);
 
-    return PhysicalDisplayId::fromEdidHash(id);
+    return PhysicalDisplayId::fromValue(id);
 }
 
 } // namespace android
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index 13ed754..937e3f1 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -30,27 +30,16 @@
     // Flag indicating that the display is virtual.
     static constexpr uint64_t FLAG_VIRTUAL = 1ULL << 63;
 
-    // TODO(b/162612135) Remove default constructor
+    // TODO: b/162612135 - Remove default constructor.
     DisplayId() = default;
     constexpr DisplayId(const DisplayId&) = default;
     DisplayId& operator=(const DisplayId&) = default;
 
+    static constexpr DisplayId fromValue(uint64_t value) { return DisplayId(value); }
     constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; }
 
     uint64_t value;
 
-    // For deserialization.
-    static constexpr std::optional<DisplayId> fromValue(uint64_t);
-
-    // As above, but also upcast to Id.
-    template <typename Id>
-    static constexpr std::optional<Id> fromValue(uint64_t value) {
-        if (const auto id = Id::tryCast(DisplayId(value))) {
-            return id;
-        }
-        return {};
-    }
-
 protected:
     explicit constexpr DisplayId(uint64_t id) : value(id) {}
 };
@@ -74,6 +63,9 @@
 
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
+    // TODO: b/162612135 - Remove default constructor.
+    PhysicalDisplayId() = default;
+
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
         if (id.isVirtual()) {
             return std::nullopt;
@@ -87,11 +79,6 @@
         return PhysicalDisplayId(FLAG_STABLE, port, manufacturerId, modelHash);
     }
 
-    // Returns a stable and consistent ID based exclusively on EDID information.
-    static constexpr PhysicalDisplayId fromEdidHash(uint64_t hashedEdid) {
-        return PhysicalDisplayId(hashedEdid);
-    }
-
     // Returns an unstable ID. If EDID is available using "fromEdid" is preferred.
     static constexpr PhysicalDisplayId fromPort(uint8_t port) {
         constexpr uint16_t kManufacturerId = 0;
@@ -99,8 +86,9 @@
         return PhysicalDisplayId(0, port, kManufacturerId, kModelHash);
     }
 
-    // TODO(b/162612135) Remove default constructor
-    PhysicalDisplayId() = default;
+    static constexpr PhysicalDisplayId fromValue(uint64_t value) {
+        return PhysicalDisplayId(value);
+    }
 
     constexpr uint8_t getPort() const { return static_cast<uint8_t>(value); }
 
@@ -131,8 +119,15 @@
         return std::nullopt;
     }
 
+    static constexpr VirtualDisplayId fromValue(uint64_t value) {
+        return VirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 protected:
+    struct SkipVirtualFlag {};
+    constexpr VirtualDisplayId(SkipVirtualFlag, uint64_t value) : DisplayId(value) {}
     explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {}
+
     explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
@@ -146,8 +141,12 @@
         return std::nullopt;
     }
 
+    static constexpr HalVirtualDisplayId fromValue(uint64_t value) {
+        return HalVirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 private:
-    explicit constexpr HalVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+    using VirtualDisplayId::VirtualDisplayId;
 };
 
 struct GpuVirtualDisplayId : VirtualDisplayId {
@@ -160,8 +159,12 @@
         return std::nullopt;
     }
 
+    static constexpr GpuVirtualDisplayId fromValue(uint64_t value) {
+        return GpuVirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 private:
-    explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+    using VirtualDisplayId::VirtualDisplayId;
 };
 
 // HalDisplayId is the ID of a display which is managed by HWC.
@@ -177,20 +180,13 @@
         return HalDisplayId(id);
     }
 
+    static constexpr HalDisplayId fromValue(uint64_t value) { return HalDisplayId(value); }
+
 private:
+    using DisplayId::DisplayId;
     explicit constexpr HalDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
-constexpr std::optional<DisplayId> DisplayId::fromValue(uint64_t value) {
-    if (const auto id = fromValue<PhysicalDisplayId>(value)) {
-        return id;
-    }
-    if (const auto id = fromValue<VirtualDisplayId>(value)) {
-        return id;
-    }
-    return {};
-}
-
 static_assert(sizeof(DisplayId) == sizeof(uint64_t));
 static_assert(sizeof(HalDisplayId) == sizeof(uint64_t));
 static_assert(sizeof(VirtualDisplayId) == sizeof(uint64_t));
diff --git a/libs/ui/include/ui/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
index 06be6db..c0b83b8 100644
--- a/libs/ui/include/ui/ShadowSettings.h
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -46,9 +46,6 @@
     // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
     float length = 0.f;
 
-    // Length of the cast shadow that is drawn by the client.
-    float clientDrawnLength = 0.f;
-
     // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
     // Otherwise the shadow will only be drawn around the edges of the casting layer.
     bool casterIsTranslucent = false;
@@ -58,7 +55,6 @@
     return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
             lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
             lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
-            lhs.clientDrawnLength == rhs.clientDrawnLength &&
             lhs.casterIsTranslucent == rhs.casterIsTranslucent;
 }
 
diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp
index 090d2ee..209acba 100644
--- a/libs/ui/tests/DisplayId_test.cpp
+++ b/libs/ui/tests/DisplayId_test.cpp
@@ -33,7 +33,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+    EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createPhysicalIdFromPort) {
@@ -47,7 +47,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+    EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createGpuVirtualId) {
@@ -59,7 +59,7 @@
     EXPECT_FALSE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
+    EXPECT_EQ(id, GpuVirtualDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) {
@@ -83,7 +83,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<HalVirtualDisplayId>(id.value));
+    EXPECT_EQ(id, HalVirtualDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createVirtualIdFromHalVirtualId) {
diff --git a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
index 7c255ed..5ba72af 100644
--- a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
+++ b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
@@ -113,7 +113,7 @@
     if (producer == NULL)
         goto not_valid_surface;
 
-    window = new android::Surface(producer, true);
+    window = android::sp<android::Surface>::make(producer, true);
 
     if (window == NULL)
         goto not_valid_surface;
diff --git a/services/automotive/display/AutomotiveDisplayProxyService.cpp b/services/automotive/display/AutomotiveDisplayProxyService.cpp
index d205231..afa6233 100644
--- a/services/automotive/display/AutomotiveDisplayProxyService.cpp
+++ b/services/automotive/display/AutomotiveDisplayProxyService.cpp
@@ -34,10 +34,8 @@
     sp<IBinder> displayToken = nullptr;
     sp<SurfaceControl> surfaceControl = nullptr;
     if (it == mDisplays.end()) {
-        if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
-            displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
-        }
-
+        displayToken =
+                SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId::fromValue(id));
         if (displayToken == nullptr) {
             ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
             return nullptr;
@@ -160,11 +158,8 @@
     HwDisplayConfig activeConfig;
     HwDisplayState  activeState;
 
-    sp<IBinder> displayToken;
-    if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
-        displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
-    }
-
+    sp<IBinder> displayToken =
+            SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId::fromValue(id));
     if (displayToken == nullptr) {
         ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
     } else {
@@ -197,4 +192,3 @@
 }  // namespace automotive
 }  // namespace frameworks
 }  // namespace android
-
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ba8c814..05602ef 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4554,8 +4554,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();
         }
@@ -4751,9 +4752,10 @@
 
     Result<void> result =
             verifier.processMovement(deviceId, motionEvent.getSource(), motionEvent.getAction(),
-                                     motionEvent.getPointerCount(),
+                                     motionEvent.getActionButton(), motionEvent.getPointerCount(),
                                      motionEvent.getPointerProperties(),
-                                     motionEvent.getSamplePointerCoords(), flags);
+                                     motionEvent.getSamplePointerCoords(), flags,
+                                     motionEvent.getButtonState());
     if (!result.ok()) {
         logDispatchStateLocked();
         LOG(ERROR) << "Inconsistent event: " << motionEvent << ", reason: " << result.error();
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 301632f..f2b2b6f 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -115,6 +115,7 @@
     ui::Rotation mOrientation{ui::ROTATION_0};
     FloatRect mBoundsInLogicalDisplay{};
 
+    // The button state as of the last sync.
     int32_t mButtonState;
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index fd9884b..914f5ab 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -1721,15 +1721,16 @@
             mParamContinueGesture(std::get<1>(GetParam())),
             mParamEndGesture(std::get<2>(GetParam())),
             mDeviceContext(*mDevice, EVENTHUB_ID),
-            mConverter(*mReader->getContext(), mDeviceContext, DEVICE_ID),
-            mVerifier("Test verifier") {
+            mConverter(*mReader->getContext(), mDeviceContext, DEVICE_ID) {
         mConverter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+        input_flags::enable_button_state_verification(true);
+        mVerifier = std::make_unique<InputVerifier>("Test verifier");
     }
 
     base::Result<void> processMotionArgs(NotifyMotionArgs arg) {
-        return mVerifier.processMovement(arg.deviceId, arg.source, arg.action,
-                                         arg.getPointerCount(), arg.pointerProperties.data(),
-                                         arg.pointerCoords.data(), arg.flags);
+        return mVerifier->processMovement(arg.deviceId, arg.source, arg.action, arg.actionButton,
+                                          arg.getPointerCount(), arg.pointerProperties.data(),
+                                          arg.pointerCoords.data(), arg.flags, arg.buttonState);
     }
 
     void verifyArgsFromGesture(const Gesture& gesture, size_t gestureIndex) {
@@ -1755,7 +1756,7 @@
 
     InputDeviceContext mDeviceContext;
     GestureConverter mConverter;
-    InputVerifier mVerifier;
+    std::unique_ptr<InputVerifier> mVerifier;
 };
 
 TEST_P(GestureConverterConsistencyTest, ButtonChangesDuringGesture) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index c0e2060..7cc4ff7 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -12405,43 +12405,69 @@
     }
 
     void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) {
+        bool consumeButtonPress = false;
         switch (fromSource) {
-            case AINPUT_SOURCE_TOUCHSCREEN:
+            case AINPUT_SOURCE_TOUCHSCREEN: {
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
                                            ui::LogicalDisplayId::DEFAULT, {50, 50}))
                         << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
                 break;
-            case AINPUT_SOURCE_STYLUS:
+            }
+            case AINPUT_SOURCE_STYLUS: {
+                PointerBuilder pointer = PointerBuilder(0, ToolType::STYLUS).x(50).y(50);
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionEvent(*mDispatcher,
                                             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                                AINPUT_SOURCE_STYLUS)
                                                     .buttonState(
                                                             AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                                    .pointer(PointerBuilder(0, ToolType::STYLUS)
-                                                                     .x(50)
-                                                                     .y(50))
+                                                    .pointer(pointer)
                                                     .build()));
+                ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                          injectMotionEvent(*mDispatcher,
+                                            MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                                                               AINPUT_SOURCE_STYLUS)
+                                                    .actionButton(
+                                                            AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                                    .buttonState(
+                                                            AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                                    .pointer(pointer)
+                                                    .build()));
+                consumeButtonPress = true;
                 break;
-            case AINPUT_SOURCE_MOUSE:
+            }
+            case AINPUT_SOURCE_MOUSE: {
+                PointerBuilder pointer =
+                        PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50);
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionEvent(*mDispatcher,
                                             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                                AINPUT_SOURCE_MOUSE)
                                                     .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                                    .pointer(PointerBuilder(MOUSE_POINTER_ID,
-                                                                            ToolType::MOUSE)
-                                                                     .x(50)
-                                                                     .y(50))
+                                                    .pointer(pointer)
                                                     .build()));
+                ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                          injectMotionEvent(*mDispatcher,
+                                            MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                                                               AINPUT_SOURCE_MOUSE)
+                                                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                                    .pointer(pointer)
+                                                    .build()));
+                consumeButtonPress = true;
                 break;
-            default:
+            }
+            default: {
                 FAIL() << "Source " << fromSource << " doesn't support drag and drop";
+            }
         }
 
         // Window should receive motion event.
         mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+        if (consumeButtonPress) {
+            mWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+        }
         // Spy window should also receive motion event
         mSpyWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     }
@@ -12641,6 +12667,16 @@
     // Move to another window and release button, expect to drop item.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                                   AINPUT_SOURCE_STYLUS)
+                                        .actionButton(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
@@ -12882,6 +12918,18 @@
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
+                                                         .x(150)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                         .buttonState(0)
                                         .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index 31db2fe..abce931 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -48,9 +48,9 @@
         auto [it, _] = mVerifiers.emplace(args.displayId, "Fuzz Verifier");
         InputVerifier& verifier = it->second;
         const Result<void> result =
-                verifier.processMovement(args.deviceId, args.source, args.action,
+                verifier.processMovement(args.deviceId, args.source, args.action, args.actionButton,
                                          args.getPointerCount(), args.pointerProperties.data(),
-                                         args.pointerCoords.data(), args.flags);
+                                         args.pointerCoords.data(), args.flags, args.buttonState);
         if (result.ok()) {
             return args;
         }
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 77bf145..6088e25 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -110,8 +110,8 @@
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this),
                            "MirrorRoot-" + std::to_string(displayId), 0 /* flags */,
                            gui::LayerMetadata());
-    std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
-    status_t status = mFlinger->mirrorDisplay(*id, args, *outResult);
+    const DisplayId id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
+    status_t status = mFlinger->mirrorDisplay(id, args, *outResult);
     return binderStatusFromStatusT(status);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index e876693..780758e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -19,6 +19,7 @@
 #include <optional>
 #include <ostream>
 #include <unordered_set>
+#include "aidl/android/hardware/graphics/composer3/Composition.h"
 #include "ui/LayerStack.h"
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -36,10 +37,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-namespace aidl::android::hardware::graphics::composer3 {
-enum class Composition;
-}
-
 namespace android {
 
 class Fence;
@@ -182,10 +179,27 @@
     // Whether the layer should be rendered with rounded corners.
     virtual bool hasRoundedCorners() const = 0;
     virtual void setWasClientComposed(const sp<Fence>&) {}
-    virtual void setHwcCompositionType(
-            aidl::android::hardware::graphics::composer3::Composition) = 0;
-    virtual aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
-            const = 0;
+
+    // These fields are all copied from the last written HWC state.
+    // This state is only used for debugging purposes.
+    struct HwcLayerDebugState {
+        aidl::android::hardware::graphics::composer3::Composition lastCompositionType =
+                aidl::android::hardware::graphics::composer3::Composition::INVALID;
+        // Corresponds to passing an alpha of 0 to HWC2::Layer::setPlaneAlpha.
+        bool wasSkipped = false;
+
+        // Indicates whether the compositionengine::OutputLayer had properties overwritten.
+        // Not directly passed to HWC.
+        bool wasOverridden = false;
+
+        // Corresponds to the GraphicBuffer ID of the buffer passed to HWC2::Layer::setBuffer.
+        // This buffer corresponds to a CachedSet that the LayerFE was flattened to.
+        uint64_t overrideBufferId = 0;
+    };
+
+    // Used for debugging purposes, e.g. perfetto tracing, dumpsys.
+    virtual void setLastHwcState(const LayerFE::HwcLayerDebugState &hwcState) = 0;
+    virtual const HwcLayerDebugState &getLastHwcState() const = 0;
 
     virtual const gui::LayerMetadata* getMetadata() const = 0;
     virtual const gui::LayerMetadata* getRelativeMetadata() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 7744b8b..d2a5a20 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -59,9 +59,9 @@
     MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*());
     MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*());
     MOCK_METHOD0(onPictureProfileCommitted, void());
-    MOCK_METHOD(void, setHwcCompositionType,
-                (aidl::android::hardware::graphics::composer3::Composition), (override));
-    MOCK_METHOD(aidl::android::hardware::graphics::composer3::Composition, getHwcCompositionType,
+    MOCK_METHOD(void, setLastHwcState,
+                (const HwcLayerDebugState&), (override));
+    MOCK_METHOD(const HwcLayerDebugState&, getLastHwcState,
                 (), (const, override));
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 9b66f01..9d67122 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -502,6 +502,15 @@
 
     editState().hwc->stateOverridden = isOverridden;
     editState().hwc->layerSkipped = skipLayer;
+
+
+    // Save the final HWC state for debugging purposes, e.g. perfetto tracing, dumpsys.
+    getLayerFE().setLastHwcState({.lastCompositionType = editState().hwc->hwcCompositionType,
+                                  .wasSkipped = skipLayer,
+                                  .wasOverridden = isOverridden,
+                                  .overrideBufferId = editState().overrideInfo.buffer
+                                          ? editState().overrideInfo.buffer.get()->getId()
+                                          : 0});
 }
 
 void OutputLayer::writeOutputDependentGeometryStateToHWC(HWC2::Layer* hwcLayer,
@@ -867,7 +876,6 @@
     if (outputDependentState.hwc->hwcCompositionType != requestedCompositionType ||
         (outputDependentState.hwc->layerSkipped && !skipLayer)) {
         outputDependentState.hwc->hwcCompositionType = requestedCompositionType;
-        getLayerFE().setHwcCompositionType(requestedCompositionType);
 
         if (auto error = hwcLayer->setCompositionType(requestedCompositionType);
             error != hal::Error::NONE) {
@@ -965,7 +973,13 @@
     }
 
     hwcState.hwcCompositionType = compositionType;
-    getLayerFE().setHwcCompositionType(compositionType);
+
+    getLayerFE().setLastHwcState({.lastCompositionType = hwcState.hwcCompositionType,
+                                  .wasSkipped = hwcState.layerSkipped,
+                                  .wasOverridden = hwcState.stateOverridden,
+                                  .overrideBufferId = state.overrideInfo.buffer
+                                          ? state.overrideInfo.buffer.get()->getId()
+                                          : 0});
 }
 
 void OutputLayer::prepareForDeviceLayerRequests() {
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 523ef7b..839bd79 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -403,11 +403,8 @@
     if (forceUpdate || requested.what & layer_state_t::eSidebandStreamChanged) {
         sidebandStream = requested.sidebandStream;
     }
-    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged ||
-        requested.what & layer_state_t::eClientDrawnShadowsChanged) {
-        shadowSettings.length =
-                requested.clientDrawnShadowRadius > 0 ? 0.f : requested.shadowRadius;
-        shadowSettings.clientDrawnLength = requested.clientDrawnShadowRadius;
+    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
+        shadowSettings.length = requested.shadowRadius;
     }
 
     if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
@@ -537,12 +534,13 @@
     }
 }
 
-char LayerSnapshot::classifyCompositionForDebug(Composition compositionType) const {
+char LayerSnapshot::classifyCompositionForDebug(
+        const compositionengine::LayerFE::HwcLayerDebugState& hwcState) const {
     if (!isVisible) {
         return '.';
     }
 
-    switch (compositionType) {
+    switch (hwcState.lastCompositionType) {
         case Composition::INVALID:
             return 'i';
         case Composition::SOLID_COLOR:
@@ -561,21 +559,21 @@
     }
 
     char code = '.'; // Default to invisible
-    if (hasBufferOrSidebandStream()) {
-        code = 'b';
-    } else if (fillsColor()) {
-        code = 'c'; // Solid color
-    } else if (hasBlur()) {
+    if (hasBlur()) {
         code = 'l'; // Blur
     } else if (hasProtectedContent) {
         code = 'p'; // Protected content
-    } else if (drawShadows()) {
-        code = 's'; // Shadow
     } else if (roundedCorner.hasRoundedCorners()) {
         code = 'r'; // Rounded corners
+    } else if (drawShadows()) {
+        code = 's'; // Shadow
+    } else if (fillsColor()) {
+        code = 'c'; // Solid color
+    } else if (hasBufferOrSidebandStream()) {
+        code = 'b';
     }
 
-    if (compositionType == Composition::CLIENT) {
+    if (hwcState.lastCompositionType == Composition::CLIENT) {
         return static_cast<char>(std::toupper(code));
     } else {
         return code;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 04b9f3b..69120bd 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -24,6 +24,7 @@
 #include "RequestedLayerState.h"
 #include "Scheduler/LayerInfo.h"
 #include "android-base/stringprintf.h"
+#include "compositionengine/LayerFE.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -163,7 +164,7 @@
     // Returns a char summarizing the composition request
     // This function tries to maintain parity with planner::Plan chars.
     char classifyCompositionForDebug(
-            aidl::android::hardware::graphics::composer3::Composition compositionType) const;
+            const compositionengine::LayerFE::HwcLayerDebugState& hwcState) const;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 1d53e71..58c235e 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -108,7 +108,6 @@
     surfaceDamageRegion = Region::INVALID_REGION;
     cornerRadius = 0.0f;
     clientDrawnCornerRadius = 0.0f;
-    clientDrawnShadowRadius = 0.0f;
     backgroundBlurRadius = 0;
     api = -1;
     hasColorTransform = false;
@@ -355,11 +354,6 @@
         clientDrawnCornerRadius = clientState.clientDrawnCornerRadius;
         changes |= RequestedLayerState::Changes::Geometry;
     }
-
-    if (clientState.what & layer_state_t::eClientDrawnShadowsChanged) {
-        clientDrawnShadowRadius = clientState.clientDrawnShadowRadius;
-        changes |= RequestedLayerState::Changes::Geometry;
-    }
 }
 
 ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const {
@@ -646,7 +640,6 @@
             layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged |
             layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eClientDrawnCornerRadiusChanged |
-            layer_state_t::eClientDrawnShadowsChanged |
             layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged |
             layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
             layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 725a782..b619268 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -428,13 +428,12 @@
     return mReleaseFencePromiseStatus;
 }
 
-void LayerFE::setHwcCompositionType(
-        aidl::android::hardware::graphics::composer3::Composition type) {
-    mLastHwcCompositionType = type;
+void LayerFE::setLastHwcState(const LayerFE::HwcLayerDebugState &state) {
+    mLastHwcState = state;
 }
 
-aidl::android::hardware::graphics::composer3::Composition LayerFE::getHwcCompositionType() const {
-    return mLastHwcCompositionType;
-}
+const LayerFE::HwcLayerDebugState& LayerFE::getLastHwcState() const {
+    return mLastHwcState;
+};
 
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 64ec278..a537456 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -59,9 +59,10 @@
     void setReleaseFence(const FenceResult& releaseFence) override;
     LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
     void onPictureProfileCommitted() override;
-    void setHwcCompositionType(aidl::android::hardware::graphics::composer3::Composition) override;
-    aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
-            const override;
+
+    // Used for debugging purposes, e.g. perfetto tracing, dumpsys.
+    void setLastHwcState(const HwcLayerDebugState &state) override;
+    const HwcLayerDebugState &getLastHwcState() const override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -93,8 +94,7 @@
     std::string mName;
     std::promise<FenceResult> mReleaseFence;
     ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
-    aidl::android::hardware::graphics::composer3::Composition mLastHwcCompositionType =
-            aidl::android::hardware::graphics::composer3::Composition::INVALID;
+    HwcLayerDebugState mLastHwcState;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1dc7b2e..1163390 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1163,8 +1163,8 @@
     }
 
     Mutex::Autolock lock(mStateLock);
-    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
-    const auto displayOpt = mPhysicalDisplays.get(*id).and_then(getDisplayDeviceAndSnapshot());
+    const PhysicalDisplayId id = PhysicalDisplayId::fromValue(static_cast<uint64_t>(displayId));
+    const auto displayOpt = mPhysicalDisplays.get(id).and_then(getDisplayDeviceAndSnapshot());
 
     if (!displayOpt) {
         return NAME_NOT_FOUND;
@@ -1286,9 +1286,9 @@
 
     Mutex::Autolock lock(mStateLock);
 
-    const auto id_ =
-            DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(physicalDisplayId));
-    const auto displayOpt = mPhysicalDisplays.get(*id_).and_then(getDisplayDeviceAndSnapshot());
+    const PhysicalDisplayId id =
+            PhysicalDisplayId::fromValue(static_cast<uint64_t>(physicalDisplayId));
+    const auto displayOpt = mPhysicalDisplays.get(id).and_then(getDisplayDeviceAndSnapshot());
 
     if (!displayOpt) {
         return NAME_NOT_FOUND;
@@ -2977,6 +2977,8 @@
     int index = 0;
     ftl::StaticVector<char, WorkloadTracer::COMPOSITION_SUMMARY_SIZE> compositionSummary;
     auto lastLayerStack = ui::INVALID_LAYER_STACK;
+
+    uint64_t prevOverrideBufferId = 0;
     for (auto& [layer, layerFE] : layers) {
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
         if (lastLayerStack != layerFE->mSnapshot->outputFilter.layerStack) {
@@ -2986,8 +2988,37 @@
             }
             lastLayerStack = layerFE->mSnapshot->outputFilter.layerStack;
         }
+
+        // If there are N layers in a cached set they should all share the same buffer id.
+        // The first layer in the cached set will be not skipped and layers 1..N-1 will be skipped.
+        // We expect all layers in the cached set to be marked as composited by HWC.
+        // Here is a made up example of how it is visualized
+        //
+        //      [b:rrc][s:cc]
+        //
+        // This should be interpreted to mean that there are 2 cached sets.
+        // So there are only 2 non skipped layers -- b and s.
+        // The layers rrc and cc are flattened into layers b and s respectively.
+        const LayerFE::HwcLayerDebugState &hwcState = layerFE->getLastHwcState();
+        if (hwcState.overrideBufferId != prevOverrideBufferId) {
+            // End the existing run.
+            if (prevOverrideBufferId) {
+                compositionSummary.push_back(']');
+            }
+            // Start a new run.
+            if (hwcState.overrideBufferId) {
+                compositionSummary.push_back('[');
+            }
+        }
+
         compositionSummary.push_back(
-                layerFE->mSnapshot->classifyCompositionForDebug(layerFE->getHwcCompositionType()));
+                layerFE->mSnapshot->classifyCompositionForDebug(hwcState));
+
+        if (hwcState.overrideBufferId && !hwcState.wasSkipped) {
+                compositionSummary.push_back(':');
+        }
+        prevOverrideBufferId = hwcState.overrideBufferId;
+
         if (layerFE->mSnapshot->hasEffect()) {
             compositedWorkload |= adpf::Workload::EFFECTS;
         }
@@ -2999,6 +3030,10 @@
             mActivePictureTracker.onLayerComposed(*layer, *layerFE, compositionResult);
         }
     }
+    // End the last run.
+    if (prevOverrideBufferId) {
+        compositionSummary.push_back(']');
+    }
 
     // Concisely describe the layers composited this frame using single chars. GPU composited layers
     // are uppercase, DPU composited are lowercase. Special chars denote effects (blur, shadow,
@@ -6731,8 +6766,9 @@
                         return getDefaultDisplayDevice()->getDisplayToken().promote();
                     }
 
-                    if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(value)) {
-                        return getPhysicalDisplayToken(*id);
+                    if (const auto token =
+                                getPhysicalDisplayToken(PhysicalDisplayId::fromValue(value))) {
+                        return token;
                     }
 
                     ALOGE("Invalid physical display ID");
@@ -6830,10 +6866,10 @@
             case 1040: {
                 auto future = mScheduler->schedule([&] {
                     n = data.readInt32();
-                    std::optional<PhysicalDisplayId> inputId = std::nullopt;
+                    PhysicalDisplayId inputId;
                     if (uint64_t inputDisplayId; data.readUint64(&inputDisplayId) == NO_ERROR) {
-                        inputId = DisplayId::fromValue<PhysicalDisplayId>(inputDisplayId);
-                        if (!inputId || getPhysicalDisplayToken(*inputId)) {
+                        inputId = PhysicalDisplayId::fromValue(inputDisplayId);
+                        if (!getPhysicalDisplayToken(inputId)) {
                             ALOGE("No display with id: %" PRIu64, inputDisplayId);
                             return NAME_NOT_FOUND;
                         }
@@ -6842,7 +6878,7 @@
                         Mutex::Autolock lock(mStateLock);
                         mLayerCachingEnabled = n != 0;
                         for (const auto& [_, display] : mDisplays) {
-                            if (!inputId || *inputId == display->getPhysicalId()) {
+                            if (inputId == display->getPhysicalId()) {
                                 display->enableLayerCaching(mLayerCachingEnabled);
                             }
                         }
@@ -6925,11 +6961,10 @@
                         int64_t arg1 = data.readInt64();
                         int64_t arg2 = data.readInt64();
                         // Enable mirroring for one display
-                        const auto display1id = DisplayId::fromValue(arg1);
                         auto mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(
-                                display1id.value());
-                        auto id2 = DisplayId::fromValue<PhysicalDisplayId>(arg2);
-                        const auto token2 = getPhysicalDisplayToken(*id2);
+                                DisplayId::fromValue(arg1));
+                        const auto token2 =
+                                getPhysicalDisplayToken(PhysicalDisplayId::fromValue(arg2));
                         ui::LayerStack layerStack;
                         {
                             Mutex::Autolock lock(mStateLock);
@@ -8714,8 +8749,8 @@
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
-    *outDisplay = mFlinger->getPhysicalDisplayToken(*id);
+    const PhysicalDisplayId id = PhysicalDisplayId::fromValue(static_cast<uint64_t>(displayId));
+    *outDisplay = mFlinger->getPhysicalDisplayToken(id);
     return binder::Status::ok();
 }
 
diff --git a/services/surfaceflinger/common/include/common/WorkloadTracer.h b/services/surfaceflinger/common/include/common/WorkloadTracer.h
index 39b6fa1..c4074f7 100644
--- a/services/surfaceflinger/common/include/common/WorkloadTracer.h
+++ b/services/surfaceflinger/common/include/common/WorkloadTracer.h
@@ -23,7 +23,7 @@
 
 static constexpr int32_t COMPOSITION_TRACE_COOKIE = 1;
 static constexpr int32_t POST_COMPOSITION_TRACE_COOKIE = 2;
-static constexpr size_t COMPOSITION_SUMMARY_SIZE = 20;
+static constexpr size_t COMPOSITION_SUMMARY_SIZE = 64;
 static constexpr const char* TRACK_NAME = "CriticalWorkload";
 
 } // namespace android::WorkloadTracer
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index ee5d919..7910e77 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -497,17 +497,6 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
-    void setClientDrawnShadowRadius(uint32_t id, float clientDrawnShadowRadius) {
-        std::vector<QueuedTransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eClientDrawnShadowsChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.clientDrawnShadowRadius = clientDrawnShadowRadius;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
     void setShadowRadius(uint32_t id, float shadowRadius) {
         std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 3fead93..81bfc97 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -193,7 +193,7 @@
     }
 };
 
-template <uint64_t displayId>
+template <VirtualDisplayId::BaseId displayId>
 struct DisplayIdGetter<HalVirtualDisplayIdType<displayId>> {
     static HalVirtualDisplayId get() { return HalVirtualDisplayId(displayId); }
 };
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 453c053..7aad84b 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1505,14 +1505,6 @@
     EXPECT_EQ(getSnapshot({.id = 111})->roundedCorner.radius.x, RADIUS);
 }
 
-TEST_F(LayerSnapshotTest, ignoreShadows) {
-    static constexpr float SHADOW_RADIUS = 123.f;
-    setClientDrawnShadowRadius(1, SHADOW_RADIUS);
-    setShadowRadius(1, SHADOW_RADIUS);
-    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot({.id = 1})->shadowSettings.length, 0.f);
-}
-
 TEST_F(LayerSnapshotTest, setShadowRadius) {
     static constexpr float SHADOW_RADIUS = 123.f;
     setShadowRadius(1, SHADOW_RADIUS);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 2d3ebb4..aadff76 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -27,7 +27,8 @@
 
 class CreateDisplayTest : public DisplayTransactionTest {
 public:
-    void createDisplayWithRequestedRefreshRate(const std::string& name, uint64_t displayId,
+    void createDisplayWithRequestedRefreshRate(const std::string& name,
+                                               VirtualDisplayId::BaseId baseId,
                                                float pacesetterDisplayRefreshRate,
                                                float requestedRefreshRate,
                                                float expectedAdjustedRefreshRate) {
@@ -49,12 +50,10 @@
         EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate));
         EXPECT_EQ(name.c_str(), display.displayName);
 
-        std::optional<VirtualDisplayId> vid =
-                DisplayId::fromValue<VirtualDisplayId>(displayId | DisplayId::FLAG_VIRTUAL);
-        ASSERT_TRUE(vid.has_value());
-
+        const VirtualDisplayId vid = GpuVirtualDisplayId(baseId);
         sp<DisplayDevice> device =
-                mFlinger.createVirtualDisplayDevice(displayToken, *vid, requestedRefreshRate);
+                mFlinger.createVirtualDisplayDevice(displayToken, vid, requestedRefreshRate);
+
         EXPECT_TRUE(device->isVirtual());
         device->adjustRefreshRate(Fps::fromValue(pacesetterDisplayRefreshRate));
         // verifying desired value