Merge "SF: Add EDID-ID Fabrication Logic" into main
diff --git a/include/android/input.h b/include/android/input.h
index 5f44550..ee98d7a 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -849,7 +849,6 @@
  * 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 */
@@ -862,7 +861,6 @@
     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 7d3fb46..14dd463 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -47,10 +47,9 @@
     InputVerifier(const std::string& name);
 
     android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action,
-                                                int32_t actionButton, uint32_t pointerCount,
+                                                uint32_t pointerCount,
                                                 const PointerProperties* pointerProperties,
-                                                const PointerCoords* pointerCoords, int32_t flags,
-                                                int32_t buttonState);
+                                                const PointerCoords* pointerCoords, int32_t flags);
 
     void resetDevice(int32_t deviceId);
 
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index f191b97..cdc53ff 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -853,8 +853,12 @@
 
 void IPCThreadState::stopProcess(bool /*immediate*/)
 {
-    ALOGI("IPCThreadState::stopProcess() (deprecated) called. Exiting process.");
-    exit(0);
+    //ALOGI("**** STOPPING PROCESS");
+    (void)flushCommands();
+    int fd = mProcess->mDriverFD;
+    mProcess->mDriverFD = -1;
+    close(fd);
+    //kill(getpid(), SIGKILL);
 }
 
 status_t IPCThreadState::transact(int32_t handle,
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index 1fde45b..dce7778 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -41,6 +41,7 @@
     ],
 
     srcs: [
+        "FeatureOverrides.cpp",
         "GpuStatsInfo.cpp",
         "GraphicsEnv.cpp",
         "IGpuService.cpp",
diff --git a/libs/graphicsenv/FeatureOverrides.cpp b/libs/graphicsenv/FeatureOverrides.cpp
new file mode 100644
index 0000000..6974da9
--- /dev/null
+++ b/libs/graphicsenv/FeatureOverrides.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <graphicsenv/FeatureOverrides.h>
+
+#include <android-base/stringprintf.h>
+
+namespace android {
+
+using base::StringAppendF;
+
+std::string FeatureConfig::toString() const {
+    std::string result;
+    StringAppendF(&result, "Feature: %s\n", mFeatureName.c_str());
+    StringAppendF(&result, "      Status: %s\n", mEnabled ? "enabled" : "disabled");
+
+    return result;
+}
+
+std::string FeatureOverrides::toString() const {
+    std::string result;
+    result.append("Global Features:\n");
+    for (auto& cfg : mGlobalFeatures) {
+        result.append("  " + cfg.toString());
+    }
+    result.append("\n");
+    result.append("Package Features:\n");
+    for (const auto& packageFeature : mPackageFeatures) {
+        result.append("  Package:");
+        StringAppendF(&result, " %s\n", packageFeature.first.c_str());
+        for (auto& cfg : packageFeature.second) {
+            result.append("    " + cfg.toString());
+        }
+    }
+
+    return result;
+}
+
+} // namespace android
diff --git a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
index 2ef54ad..2b94187 100644
--- a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
+++ b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
@@ -27,6 +27,7 @@
     FeatureConfig() = default;
     FeatureConfig(const FeatureConfig&) = default;
     virtual ~FeatureConfig() = default;
+    std::string toString() const;
 
     std::string mFeatureName;
     bool mEnabled;
@@ -41,6 +42,7 @@
     FeatureOverrides() = default;
     FeatureOverrides(const FeatureOverrides&) = default;
     virtual ~FeatureOverrides() = default;
+    std::string toString() const;
 
     std::vector<FeatureConfig> mGlobalFeatures;
     /* Key: Package Name, Value: Package's Feature Configs */
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index d9c10ad..389fb7f 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -91,13 +91,6 @@
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_BACK",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY",
-        "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY",
         "--allowlist-var=MAX_POINTER_ID",
         "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
         "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index d388d48..56ccaab 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, actionButton,
-                                                             pointerCount, pointerProperties,
-                                                             pointerCoords, flags, buttonState);
+        Result<void> result =
+                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+                                               pointerProperties, pointerCoords, flags);
         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 e8d4c73..cec2445 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -34,10 +34,9 @@
       : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
 
 Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
-                                            int32_t actionButton, uint32_t pointerCount,
+                                            uint32_t pointerCount,
                                             const PointerProperties* pointerProperties,
-                                            const PointerCoords* pointerCoords, int32_t flags,
-                                            int32_t buttonState) {
+                                            const PointerCoords* pointerCoords, int32_t flags) {
     std::vector<RustPointerProperties> rpp;
     for (size_t i = 0; i < pointerCount; i++) {
         rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
@@ -45,9 +44,7 @@
     rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
     rust::String errorMessage =
             android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
-                                                       actionButton, properties,
-                                                       static_cast<uint32_t>(flags),
-                                                       static_cast<uint32_t>(buttonState));
+                                                       properties, static_cast<uint32_t>(flags));
     if (errorMessage.empty()) {
         return {};
     } else {
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 5e487ab..90f509d 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -101,7 +101,6 @@
 
 /// A rust enum representation of a MotionEvent action.
 #[repr(u32)]
-#[derive(PartialEq)]
 pub enum MotionAction {
     /// ACTION_DOWN
     Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
@@ -195,27 +194,6 @@
 }
 
 bitflags! {
-    /// MotionEvent buttons.
-    #[derive(Clone, Copy, Debug, Default, PartialEq)]
-    pub struct MotionButton: u32 {
-        /// Primary button (e.g. the left mouse button)
-        const Primary = input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY;
-        /// Secondary button (e.g. the right mouse button)
-        const Secondary = input_bindgen::AMOTION_EVENT_BUTTON_SECONDARY;
-        /// Tertiary button (e.g. the middle mouse button)
-        const Tertiary = input_bindgen::AMOTION_EVENT_BUTTON_TERTIARY;
-        /// Back button
-        const Back = input_bindgen::AMOTION_EVENT_BUTTON_BACK;
-        /// Forward button
-        const Forward = input_bindgen::AMOTION_EVENT_BUTTON_FORWARD;
-        /// Primary stylus button
-        const StylusPrimary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY;
-        /// Secondary stylus button
-        const StylusSecondary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY;
-    }
-}
-
-bitflags! {
     /// MotionEvent flags.
     /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
     /// The flag values are redefined here as a bitflags API.
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 55dc56f..b1d7760 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,14 +17,13 @@
 //! Contains the InputVerifier, used to validate a stream of input events.
 
 use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass};
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
 use log::info;
 use std::collections::HashMap;
 use std::collections::HashSet;
 
 fn verify_event(
     action: MotionAction,
-    action_button: MotionButton,
     pointer_properties: &[RustPointerProperties],
     flags: &MotionFlags,
 ) -> Result<(), String> {
@@ -32,14 +31,6 @@
     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
@@ -69,118 +60,17 @@
             }
         }
 
-        MotionAction::ButtonPress | MotionAction::ButtonRelease => {
-            let button_count = action_button.iter().count();
-            if button_count != 1 {
-                return Err(format!(
-                    "Invalid {action} event: must specify a single action button, not \
-                     {button_count} action buttons"
-                ));
-            }
-        }
-
         _ => {}
     }
     Ok(())
 }
 
-/// Keeps track of the button state for a single device and verifies events against it.
-#[derive(Default)]
-struct ButtonVerifier {
-    /// The current button state of the device.
-    button_state: MotionButton,
-
-    /// The set of "pending buttons", which were seen in the last DOWN event's button state but
-    /// for which we haven't seen BUTTON_PRESS events yet.
-    ///
-    /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
-    /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
-    /// for those buttons, building up to a button state matching that of the DOWN. For example, if
-    /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
-    /// this sequence:
-    ///
-    /// | Action         | Action button | Button state           |
-    /// |----------------|---------------|------------------------|
-    /// | `HOVER_EXIT`   | -             | -                      |
-    /// | `DOWN`         | -             | `PRIMARY`, `SECONDARY` |
-    /// | `BUTTON_PRESS` | `PRIMARY`     | `PRIMARY`              |
-    /// | `BUTTON_PRESS` | `SECONDARY`   | `PRIMARY`, `SECONDARY` |
-    /// | `MOVE`         | -             | `PRIMARY`, `SECONDARY` |
-    pending_buttons: MotionButton,
-}
-
-impl ButtonVerifier {
-    pub fn process_action(
-        &mut self,
-        action: MotionAction,
-        action_button: MotionButton,
-        button_state: MotionButton,
-    ) -> Result<(), String> {
-        if !self.pending_buttons.is_empty() {
-            // We just saw a DOWN with some additional buttons in its state, so it should be
-            // immediately followed by ButtonPress events for those buttons.
-            if action != MotionAction::ButtonPress || !self.pending_buttons.contains(action_button)
-            {
-                return Err(format!(
-                    "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {} with \
-                     action button {:?}",
-                    self.pending_buttons, action, action_button
-                ));
-            } else {
-                self.pending_buttons -= action_button;
-            }
-        }
-        let expected_state = match action {
-            MotionAction::Down => {
-                if self.button_state - button_state != MotionButton::empty() {
-                    return Err(format!(
-                        "DOWN event button state is missing {:?}",
-                        self.button_state - button_state
-                    ));
-                }
-                self.pending_buttons = button_state - self.button_state;
-                // We've already checked that the state isn't missing any already-down buttons, and
-                // extra buttons are valid on DOWN actions, so bypass the expected state check.
-                button_state
-            }
-            MotionAction::ButtonPress => {
-                if self.button_state.contains(action_button) {
-                    return Err(format!(
-                        "Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
-                    ));
-                }
-                self.button_state | action_button
-            }
-            MotionAction::ButtonRelease => {
-                if !self.button_state.contains(action_button) {
-                    return Err(format!(
-                        "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}"
-                    ));
-                }
-                self.button_state - action_button
-            }
-            _ => self.button_state,
-        };
-        if button_state != expected_state {
-            return Err(format!(
-                "Expected {action} button state to be {expected_state:?}, but was {button_state:?}"
-            ));
-        }
-        // DOWN events can have pending buttons, so don't update the state for them.
-        if action != MotionAction::Down {
-            self.button_state = button_state;
-        }
-        Ok(())
-    }
-}
-
 /// The InputVerifier is used to validate a stream of input events.
 pub struct InputVerifier {
     name: String,
     should_log: bool,
     touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
     hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
-    button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>,
 }
 
 impl InputVerifier {
@@ -196,22 +86,18 @@
             should_log,
             touching_pointer_ids_by_device: HashMap::new(),
             hovering_pointer_ids_by_device: HashMap::new(),
-            button_verifier_by_device: HashMap::new(),
         }
     }
 
     /// Process a pointer movement event from an InputDevice.
     /// If the event is not valid, we return an error string that describes the issue.
-    #[allow(clippy::too_many_arguments)]
     pub fn process_movement(
         &mut self,
         device_id: DeviceId,
         source: Source,
         action: u32,
-        action_button: MotionButton,
         pointer_properties: &[RustPointerProperties],
         flags: MotionFlags,
-        button_state: MotionButton,
     ) -> Result<(), String> {
         if !source.is_from_class(SourceClass::Pointer) {
             // Skip non-pointer sources like MOUSE_RELATIVE for now
@@ -228,13 +114,7 @@
             );
         }
 
-        verify_event(action.into(), action_button, pointer_properties, &flags)?;
-
-        self.button_verifier_by_device.entry(device_id).or_default().process_action(
-            action.into(),
-            action_button,
-            button_state,
-        )?;
+        verify_event(action.into(), pointer_properties, &flags)?;
 
         match action.into() {
             MotionAction::Down => {
@@ -408,7 +288,6 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::input::MotionButton;
     use crate::input_verifier::InputVerifier;
     use crate::DeviceId;
     use crate::MotionFlags;
@@ -428,10 +307,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_err());
     }
@@ -445,10 +322,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -456,10 +331,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -467,10 +340,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -484,10 +355,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -499,10 +368,8 @@
                 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
@@ -512,10 +379,8 @@
                 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
@@ -525,10 +390,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
                 &pointer_1_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -542,10 +405,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -553,10 +414,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -564,10 +423,8 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -575,10 +432,8 @@
                 DeviceId(2),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -586,10 +441,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -603,10 +456,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -614,10 +465,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::CANCELED,
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -631,10 +480,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         assert!(verifier
@@ -642,10 +489,8 @@
                 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());
     }
@@ -659,10 +504,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_err());
     }
@@ -676,10 +519,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
 
@@ -688,10 +529,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
 
@@ -700,10 +539,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
 
@@ -712,10 +549,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -729,10 +564,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
 
@@ -741,10 +574,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_err());
     }
@@ -760,10 +591,8 @@
                 DeviceId(2),
                 Source::MouseRelative,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
     }
@@ -778,10 +607,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_ok());
         // POINTER 1 DOWN
@@ -793,10 +620,8 @@
                 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
@@ -805,470 +630,8 @@
                 DeviceId(1),
                 Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
                 &pointer_properties,
                 MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn correct_button_press() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-    }
-
-    #[test]
-    fn button_press_without_action_button() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn button_press_with_multiple_action_buttons() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back | MotionButton::Forward,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back | MotionButton::Forward,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn button_press_without_action_button_in_state() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn button_release_with_action_button_in_state() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_with_action_button() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_with_action_button_and_state() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_with_button_state_change() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_missing_button_state() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn up_without_button_release() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn button_press_for_already_pressed_button() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn button_release_for_unpressed_button() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn correct_multiple_button_presses_without_down() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Forward,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back | MotionButton::Forward,
-            )
-            .is_ok());
-    }
-
-    #[test]
-    fn correct_down_with_button_press() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Secondary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
-            .is_ok());
-        // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
-        // enough BUTTON_PRESSes were sent.
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
-            .is_ok());
-    }
-
-    #[test]
-    fn down_with_button_state_change_not_followed_by_button_press() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn down_with_button_state_change_not_followed_by_enough_button_presses() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
-            .is_ok());
-        // The DOWN event itself is OK, but it needs to be immediately followed by two
-        // BUTTON_PRESSes, one for each button.
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn down_missing_already_pressed_button() {
-        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
-            .is_ok());
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
             )
             .is_err());
     }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 1d5793b..4f4ea85 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, MotionButton,
-    MotionFlags, Source,
+    DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionFlags,
+    Source,
 };
 pub use input_verifier::InputVerifier;
 pub use keyboard_classifier::KeyboardClassifier;
@@ -58,16 +58,13 @@
         type InputVerifier;
         #[cxx_name = create]
         fn create_input_verifier(name: String) -> Box<InputVerifier>;
-        #[allow(clippy::too_many_arguments)]
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
             source: u32,
             action: u32,
-            action_button: u32,
             pointer_properties: &[RustPointerProperties],
             flags: u32,
-            button_state: u32,
         ) -> String;
         fn reset_device(verifier: &mut InputVerifier, device_id: i32);
     }
@@ -122,16 +119,13 @@
     Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
 }
 
-#[allow(clippy::too_many_arguments)]
 fn process_movement(
     verifier: &mut InputVerifier,
     device_id: i32,
     source: u32,
     action: u32,
-    action_button: u32,
     pointer_properties: &[RustPointerProperties],
     flags: u32,
-    button_state: u32,
 ) -> String {
     let motion_flags = MotionFlags::from_bits(flags);
     if motion_flags.is_none() {
@@ -141,28 +135,12 @@
             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 8e0d906..5bb1d56 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, /*actionButton=*/0,
+                                     AMOTION_EVENT_ACTION_DOWN,
                                      /*pointerCount=*/properties.size(), properties.data(),
-                                     coords.data(), /*flags=*/0, /*buttonState=*/0);
+                                     coords.data(), /*flags=*/0);
     ASSERT_RESULT_OK(result);
 }
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 14d08ee..5f2d1b1 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -838,8 +838,7 @@
             LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
             LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
 
-            // save a snapshot of the activeSurface to use as input to the blur shaders
-            blurInput = activeSurface->makeImageSnapshot();
+            blurInput = activeSurface->makeTemporaryImage();
 
             // blit the offscreen framebuffer into the destination AHB. This ensures that
             // even if the blurred image does not cover the screen (for example, during
@@ -853,12 +852,9 @@
                     dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
                                               String8::format("SurfaceID|%" PRId64, id).c_str(),
                                               nullptr);
-                    dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
-                } else {
-                    activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
                 }
+                dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
             }
-
             // assign dstCanvas to canvas and ensure that the canvas state is up to date
             canvas = dstCanvas;
             surfaceAutoSaveRestore.replace(canvas);
@@ -891,12 +887,6 @@
         if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
-            // if multiple layers have blur, then we need to take a snapshot now because
-            // only the lowest layer will have blurImage populated earlier
-            if (!blurInput) {
-                blurInput = activeSurface->makeImageSnapshot();
-            }
-
             // rect to be blurred in the coordinate space of blurInput
             SkRect blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
 
@@ -920,6 +910,29 @@
 
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
+                // if multiple layers have blur, then we need to take a snapshot now because
+                // only the lowest layer will have blurImage populated earlier
+                if (!blurInput) {
+                    bool requiresCrossFadeWithBlurInput = false;
+                    if (layer.backgroundBlurRadius > 0 &&
+                        layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                        requiresCrossFadeWithBlurInput = true;
+                    }
+                    for (auto region : layer.blurRegions) {
+                        if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                            requiresCrossFadeWithBlurInput = true;
+                        }
+                    }
+                    if (requiresCrossFadeWithBlurInput) {
+                        // If we require cross fading with the blur input, we need to make sure we
+                        // make a copy of the surface to the image since we will be writing to the
+                        // surface while sampling the blurInput.
+                        blurInput = activeSurface->makeImageSnapshot();
+                    } else {
+                        blurInput = activeSurface->makeTemporaryImage();
+                    }
+                }
+
                 if (layer.backgroundBlurRadius > 0) {
                     SFTRACE_NAME("BackgroundBlur");
                     auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index cd1bd71..8edf98e 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -90,6 +90,8 @@
                                                      linearSampling, &blurMatrix);
 
     if (blurRadius < mMaxCrossFadeRadius) {
+        LOG_ALWAYS_FATAL_IF(!input);
+
         // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
         // case you might expect the matrix to simply be the canvas matrix.
         SkMatrix inputMatrix;
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 8c52c57..b03ebe3 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -64,7 +64,7 @@
             SkSamplingOptions{SkFilterMode::kLinear, SkMipmapMode::kNone},
             &paint,
             SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index ef57c30..897f5cf 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -167,14 +167,14 @@
     }
     // Next the remaining downscale blur passes.
     for (int i = 0; i < filterPasses; i++) {
-        blurInto(surfaces[i + 1], surfaces[i]->makeImageSnapshot(), kWeights[1 + i] * step, 1.0f);
+        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f);
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
-        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step,
+        blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), kWeights[4 - i] * step,
                  std::min(1.0f, filterDepth - i));
     }
-    return surfaces[0]->makeImageSnapshot();
+    return surfaces[0]->makeTemporaryImage();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index defaf6e..f71a63d 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -70,7 +70,7 @@
     paint.setShader(std::move(shader));
     paint.setBlendMode(SkBlendMode::kSrc);
     surface->getCanvas()->drawPaint(paint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index aa12cef..5dc36e6 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -104,7 +104,7 @@
     paint.setShader(std::move(shader));
     paint.setBlendMode(SkBlendMode::kSrc);
     surface->getCanvas()->drawPaint(paint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 } // namespace
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a8cd7f9..beb4c92 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -928,6 +928,7 @@
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
       : mPolicy(policy),
+        mLooper(sp<Looper>::make(false)),
         mPendingEvent(nullptr),
         mLastDropReason(DropReason::NOT_DROPPED),
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
@@ -938,6 +939,7 @@
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
         mMaximumObscuringOpacityForTouch(1.0f),
+        mConnectionManager(mLooper),
         mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
         mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID),
@@ -948,7 +950,6 @@
                         : std::move(std::unique_ptr<InputEventTimelineProcessor>(
                                   new LatencyAggregator()))),
         mLatencyTracker(*mInputEventTimelineProcessor) {
-    mLooper = sp<Looper>::make(false);
     mReporter = createInputReporter();
 
     mWindowInfoListener = sp<DispatcherWindowListener>::make(*this);
@@ -971,11 +972,6 @@
     releasePendingEventLocked();
     drainInboundQueueLocked();
     mCommandQueue.clear();
-
-    while (!mConnectionsByToken.empty()) {
-        std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannelLocked(connection, /*notify=*/false);
-    }
 }
 
 status_t InputDispatcher::start() {
@@ -1092,9 +1088,13 @@
     }
 
     // If we reached here, we have an unresponsive connection.
-    std::shared_ptr<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
+    std::shared_ptr<Connection> connection =
+            mConnectionManager.getConnection(mAnrTracker.firstToken());
     if (connection == nullptr) {
         ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
+        // As we no longer have entry for this connection, remove it form Anr tracker to prevent
+        // samme error being logged multiple times.
+        mAnrTracker.eraseToken(mAnrTracker.firstToken());
         return nextAnrCheck;
     }
     connection->responsive = false;
@@ -1344,7 +1344,7 @@
                                                      motionEntry.deviceId, mTouchStatesByDisplay);
         for (const auto& windowHandle : touchedSpies) {
             const std::shared_ptr<Connection> connection =
-                    getConnectionLocked(windowHandle->getToken());
+                    mConnectionManager.getConnection(windowHandle->getToken());
             if (connection != nullptr && connection->responsive) {
                 // This spy window could take more input. Drop all events preceding this
                 // event, so that the spy window can get a chance to receive the stream.
@@ -1727,7 +1727,8 @@
 
 void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime,
                                           std::shared_ptr<const FocusEntry> entry) {
-    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    std::shared_ptr<Connection> connection =
+            mConnectionManager.getConnection(entry->connectionToken);
     if (connection == nullptr) {
         return; // Connection has gone away
     }
@@ -1795,7 +1796,7 @@
         }
     }
 
-    auto connection = getConnectionLocked(token);
+    auto connection = mConnectionManager.getConnection(token);
     if (connection == nullptr) {
         // Window has gone away, clean up Pointer Capture state.
         mWindowTokenWithPointerCapture = nullptr;
@@ -1834,7 +1835,7 @@
         if (token == nullptr) {
             continue;
         }
-        std::shared_ptr<Connection> connection = getConnectionLocked(token);
+        std::shared_ptr<Connection> connection = mConnectionManager.getConnection(token);
         if (connection == nullptr) {
             continue; // Connection has gone away
         }
@@ -2127,7 +2128,8 @@
 
 void InputDispatcher::dispatchDragLocked(nsecs_t currentTime,
                                          std::shared_ptr<const DragEntry> entry) {
-    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    std::shared_ptr<Connection> connection =
+            mConnectionManager.getConnection(entry->connectionToken);
     if (connection == nullptr) {
         return; // Connection has gone away
     }
@@ -2371,26 +2373,6 @@
     return focusedWindowHandle;
 }
 
-/**
- * Given a list of monitors, remove the ones we cannot find a connection for, and the ones
- * that are currently unresponsive.
- */
-std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked(
-        const std::vector<Monitor>& monitors) const {
-    std::vector<Monitor> responsiveMonitors;
-    std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
-                 [](const Monitor& monitor) REQUIRES(mLock) {
-                     std::shared_ptr<Connection> connection = monitor.connection;
-                     if (!connection->responsive) {
-                         ALOGW("Unresponsive monitor %s will not get the new gesture",
-                               connection->getInputChannelName().c_str());
-                         return false;
-                     }
-                     return true;
-                 });
-    return responsiveMonitors;
-}
-
 base::Result<std::vector<InputTarget>, android::os::InputEventInjectionResult>
 InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry& entry) {
     ATRACE_CALL();
@@ -2933,7 +2915,8 @@
     const WindowInfo* windowInfo = windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+        std::shared_ptr<Connection> connection =
+                mConnectionManager.getConnection(windowHandle->getToken());
         if (connection == nullptr) {
             ALOGW("Not creating InputTarget for %s, no input channel",
                   windowHandle->getName().c_str());
@@ -2987,7 +2970,8 @@
     const WindowInfo* windowInfo = windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+        std::shared_ptr<Connection> connection =
+                mConnectionManager.getConnection(windowHandle->getToken());
         if (connection == nullptr) {
             ALOGW("Not creating InputTarget for %s, no input channel",
                   windowHandle->getName().c_str());
@@ -3022,18 +3006,30 @@
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
                                                        ui::LogicalDisplayId displayId) {
-    auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
-    if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;
+    mConnectionManager
+            .forEachGlobalMonitorConnection(displayId,
+                                            [&](const std::shared_ptr<Connection>& connection) {
+                                                if (!connection->responsive) {
+                                                    ALOGW("Ignoring unrsponsive monitor: %s",
+                                                          connection->getInputChannelName()
+                                                                  .c_str());
+                                                    return;
+                                                }
 
-    for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
-        InputTarget target{monitor.connection};
-        // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
-        // touch and global monitoring works as intended even without setting firstDownTimeInTarget.
-        // Since global monitors don't have windows, use the display transform as the raw transform.
-        target.rawTransform = mWindowInfos.getDisplayTransform(displayId);
-        target.setDefaultPointerTransform(target.rawTransform);
-        inputTargets.push_back(target);
-    }
+                                                InputTarget target{connection};
+                                                // target.firstDownTimeInTarget is not set for
+                                                // global monitors. It is only required in split
+                                                // touch and global monitoring works as intended
+                                                // even without setting firstDownTimeInTarget. Since
+                                                // global monitors don't have windows, use the
+                                                // display transform as the raw transform.
+                                                base::ScopedLockAssertion assumeLocked(mLock);
+                                                target.rawTransform =
+                                                        mWindowInfos.getDisplayTransform(displayId);
+                                                target.setDefaultPointerTransform(
+                                                        target.rawTransform);
+                                                inputTargets.push_back(target);
+                                            });
 }
 
 /**
@@ -3992,7 +3988,7 @@
 
 int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
     std::scoped_lock _l(mLock);
-    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
+    std::shared_ptr<Connection> connection = mConnectionManager.getConnection(connectionToken);
     if (connection == nullptr) {
         ALOGW("Received looper callback for unknown input channel token %p.  events=0x%x",
               connectionToken.get(), events);
@@ -4100,12 +4096,12 @@
 
 void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked(
         const CancelationOptions& options) {
-    for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
-        for (const Monitor& monitor : monitors) {
-            synthesizeCancelationEventsForConnectionLocked(monitor.connection, options,
-                                                           /*window=*/nullptr);
-        }
-    }
+    mConnectionManager.forEachGlobalMonitorConnection(
+            [&](const std::shared_ptr<Connection>& connection) {
+                base::ScopedLockAssertion assumeLocked(mLock);
+                synthesizeCancelationEventsForConnectionLocked(connection, options,
+                                                               /*window=*/nullptr);
+            });
 }
 
 void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
@@ -4123,7 +4119,7 @@
     }
 
     std::shared_ptr<Connection> resolvedConnection =
-            connection ? connection : getConnectionLocked(windowHandle->getToken());
+            connection ? connection : mConnectionManager.getConnection(windowHandle->getToken());
     if (!resolvedConnection) {
         LOG(DEBUG) << __func__ << "No connection found for window: " << windowHandle->getName();
         return;
@@ -4541,9 +4537,8 @@
                                                              args.displayId.toString().c_str()));
         Result<void> result =
                 it->second.processMovement(args.deviceId, args.source, args.action,
-                                           args.actionButton, args.getPointerCount(),
-                                           args.pointerProperties.data(), args.pointerCoords.data(),
-                                           args.flags, args.buttonState);
+                                           args.getPointerCount(), args.pointerProperties.data(),
+                                           args.pointerCoords.data(), args.flags);
         if (!result.ok()) {
             LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
         }
@@ -4744,10 +4739,9 @@
 
     Result<void> result =
             verifier.processMovement(deviceId, motionEvent.getSource(), motionEvent.getAction(),
-                                     motionEvent.getActionButton(), motionEvent.getPointerCount(),
+                                     motionEvent.getPointerCount(),
                                      motionEvent.getPointerProperties(),
-                                     motionEvent.getSamplePointerCoords(), flags,
-                                     motionEvent.getButtonState());
+                                     motionEvent.getSamplePointerCoords(), flags);
     if (!result.ok()) {
         logDispatchStateLocked();
         LOG(ERROR) << "Inconsistent event: " << motionEvent << ", reason: " << result.error();
@@ -5290,7 +5284,7 @@
         return false;
     }
 
-    std::shared_ptr<Connection> connection = getConnectionLocked(window->getToken());
+    std::shared_ptr<Connection> connection = mConnectionManager.getConnection(window->getToken());
     if (connection == nullptr) {
         ALOGW("Not sending touch to %s because there's no corresponding connection",
               window->getName().c_str());
@@ -5353,7 +5347,7 @@
     std::vector<sp<WindowInfoHandle>> newHandles;
     for (const sp<WindowInfoHandle>& handle : windowInfoHandles) {
         const WindowInfo* info = handle->getInfo();
-        if (getConnectionLocked(handle->getToken()) == nullptr) {
+        if (mConnectionManager.getConnection(handle->getToken()) == nullptr) {
             const bool noInputChannel =
                     info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
             const bool canReceiveInput =
@@ -5859,8 +5853,8 @@
 
         // Synthesize cancel for old window and down for new window.
         ScopedSyntheticEventTracer traceContext(mTracer);
-        std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
-        std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
+        std::shared_ptr<Connection> fromConnection = mConnectionManager.getConnection(fromToken);
+        std::shared_ptr<Connection> toConnection = mConnectionManager.getConnection(toToken);
         if (fromConnection != nullptr && toConnection != nullptr) {
             fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
             CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
@@ -6035,18 +6029,10 @@
 
     dump += addLinePrefix(mWindowInfos.dumpDisplayAndWindowInfo(), INDENT);
 
-    if (!mGlobalMonitorsByDisplay.empty()) {
-        for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
-            dump += StringPrintf(INDENT "Global monitors on display %s:\n",
-                                 displayId.toString().c_str());
-            dumpMonitors(dump, monitors);
-        }
-    } else {
-        dump += INDENT "Global Monitors: <none>\n";
-    }
-
     const nsecs_t currentTime = now();
 
+    dump += addLinePrefix(mConnectionManager.dump(currentTime), INDENT);
+
     // Dump recently dispatched or dropped events from oldest to newest.
     if (!mRecentQueue.empty()) {
         dump += StringPrintf(INDENT "RecentQueue: length=%zu\n", mRecentQueue.size());
@@ -6088,37 +6074,6 @@
         dump += INDENT "CommandQueue: <empty>\n";
     }
 
-    if (!mConnectionsByToken.empty()) {
-        dump += INDENT "Connections:\n";
-        for (const auto& [token, connection] : mConnectionsByToken) {
-            dump += StringPrintf(INDENT2 "%i: channelName='%s', "
-                                         "status=%s, monitor=%s, responsive=%s\n",
-                                 connection->inputPublisher.getChannel().getFd(),
-                                 connection->getInputChannelName().c_str(),
-                                 ftl::enum_string(connection->status).c_str(),
-                                 toString(connection->monitor), toString(connection->responsive));
-
-            if (!connection->outboundQueue.empty()) {
-                dump += StringPrintf(INDENT3 "OutboundQueue: length=%zu\n",
-                                     connection->outboundQueue.size());
-                dump += dumpQueue(connection->outboundQueue, currentTime);
-            }
-
-            if (!connection->waitQueue.empty()) {
-                dump += StringPrintf(INDENT3 "WaitQueue: length=%zu\n",
-                                     connection->waitQueue.size());
-                dump += dumpQueue(connection->waitQueue, currentTime);
-            }
-            std::string inputStateDump = streamableToString(connection->inputState);
-            if (!inputStateDump.empty()) {
-                dump += INDENT3 "InputState: ";
-                dump += inputStateDump + "\n";
-            }
-        }
-    } else {
-        dump += INDENT "Connections: <none>\n";
-    }
-
     if (!mTouchModePerDisplay.empty()) {
         dump += INDENT "TouchModePerDisplay:\n";
         for (const auto& [displayId, touchMode] : mTouchModePerDisplay) {
@@ -6139,16 +6094,6 @@
     dump += mTracer == nullptr ? "Disabled" : "Enabled";
 }
 
-void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const {
-    const size_t numMonitors = monitors.size();
-    for (size_t i = 0; i < numMonitors; i++) {
-        const Monitor& monitor = monitors[i];
-        const std::shared_ptr<Connection>& connection = monitor.connection;
-        dump += StringPrintf(INDENT2 "%zu: '%s', ", i, connection->getInputChannelName().c_str());
-        dump += "\n";
-    }
-}
-
 class LooperEventCallback : public LooperCallback {
 public:
     LooperEventCallback(std::function<int(int events)> callback) : mCallback(callback) {}
@@ -6174,21 +6119,10 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        const int fd = serverChannel->getFd();
-        std::shared_ptr<Connection> connection =
-                std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
-                                             mIdGenerator);
-
-        auto [_, inserted] = mConnectionsByToken.try_emplace(token, connection);
-        if (!inserted) {
-            ALOGE("Created a new connection, but the token %p is already known", token.get());
-        }
-
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
-                       nullptr);
+        mConnectionManager.createConnection(std::move(serverChannel), mIdGenerator, callback);
     } // release lock
 
     // Wake the looper because some connections have changed.
@@ -6214,23 +6148,11 @@
         }
 
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        const int fd = serverChannel->getFd();
-        std::shared_ptr<Connection> connection =
-                std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/true,
-                                             mIdGenerator);
-
-        auto [_, inserted] = mConnectionsByToken.emplace(token, connection);
-        if (!inserted) {
-            ALOGE("Created a new connection, but the token %p is already known", token.get());
-        }
-
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mGlobalMonitorsByDisplay[displayId].emplace_back(connection, pid);
-
-        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
-                       nullptr);
+        mConnectionManager.createGlobalInputMonitor(displayId, std::move(serverChannel),
+                                                    mIdGenerator, pid, callback);
     }
 
     // Wake the looper because some connections have changed.
@@ -6241,7 +6163,7 @@
 status_t InputDispatcher::removeInputChannel(const sp<IBinder>& connectionToken) {
     { // acquire lock
         std::scoped_lock _l(mLock);
-        std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
+        std::shared_ptr<Connection> connection = mConnectionManager.getConnection(connectionToken);
         if (connection == nullptr) {
             // Connection can be removed via socket hang up or an explicit call to
             // 'removeInputChannel'
@@ -6264,19 +6186,14 @@
                                                    bool notify) {
     LOG_ALWAYS_FATAL_IF(connection == nullptr);
     abortBrokenDispatchCycleLocked(connection, notify);
-    removeConnectionLocked(connection);
 
-    if (connection->monitor) {
-        removeMonitorChannelLocked(connection->getToken());
-    }
+    mAnrTracker.eraseToken(connection->getToken());
+    mConnectionManager.removeConnection(connection);
 
-    mLooper->removeFd(connection->inputPublisher.getChannel().getFd());
-
-    connection->status = Connection::Status::ZOMBIE;
     return OK;
 }
 
-void InputDispatcher::removeMonitorChannelLocked(const sp<IBinder>& connectionToken) {
+void InputDispatcher::ConnectionManager::removeMonitorChannel(const sp<IBinder>& connectionToken) {
     for (auto it = mGlobalMonitorsByDisplay.begin(); it != mGlobalMonitorsByDisplay.end();) {
         auto& [displayId, monitors] = *it;
         std::erase_if(monitors, [connectionToken](const Monitor& monitor) {
@@ -6297,7 +6214,8 @@
 }
 
 status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
-    const std::shared_ptr<Connection> requestingConnection = getConnectionLocked(token);
+    const std::shared_ptr<Connection> requestingConnection =
+            mConnectionManager.getConnection(token);
     if (!requestingConnection) {
         LOG(WARNING)
                 << "Attempted to pilfer pointers from an un-registered channel or invalid token";
@@ -6404,7 +6322,8 @@
     } // release lock
 }
 
-std::optional<gui::Pid> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
+std::optional<gui::Pid> InputDispatcher::ConnectionManager::findMonitorPidByToken(
+        const sp<IBinder>& token) const {
     for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
             if (monitor.connection->getToken() == token) {
@@ -6415,7 +6334,7 @@
     return std::nullopt;
 }
 
-std::shared_ptr<Connection> InputDispatcher::getConnectionLocked(
+std::shared_ptr<Connection> InputDispatcher::ConnectionManager::getConnection(
         const sp<IBinder>& inputConnectionToken) const {
     if (inputConnectionToken == nullptr) {
         return nullptr;
@@ -6430,19 +6349,6 @@
     return nullptr;
 }
 
-std::string InputDispatcher::getConnectionNameLocked(const sp<IBinder>& connectionToken) const {
-    std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
-    if (connection == nullptr) {
-        return "<nullptr>";
-    }
-    return connection->getInputChannelName();
-}
-
-void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) {
-    mAnrTracker.eraseToken(connection->getToken());
-    mConnectionsByToken.erase(connection->getToken());
-}
-
 void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
                                                      const std::shared_ptr<Connection>& connection,
                                                      uint32_t seq, bool handled,
@@ -6667,7 +6573,7 @@
     if (connection.monitor) {
         ALOGW("Monitor %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
-        pid = findMonitorPidByTokenLocked(connectionToken);
+        pid = mConnectionManager.findMonitorPidByToken(connectionToken);
     } else {
         // The connection is a window
         ALOGW("Window %s is unresponsive: %s", connection.getInputChannelName().c_str(),
@@ -6687,7 +6593,7 @@
     const sp<IBinder>& connectionToken = connection.getToken();
     std::optional<gui::Pid> pid;
     if (connection.monitor) {
-        pid = findMonitorPidByTokenLocked(connectionToken);
+        pid = mConnectionManager.findMonitorPidByToken(connectionToken);
     } else {
         // The connection is a window
         const sp<WindowInfoHandle> handle = mWindowInfos.findWindowHandle(connectionToken);
@@ -7248,10 +7154,10 @@
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
                                 deviceId, pointers, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
-                getConnectionLocked(newWallpaper->getToken());
+                mConnectionManager.getConnection(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
             std::shared_ptr<Connection> toConnection =
-                    getConnectionLocked(toWindowHandle->getToken());
+                    mConnectionManager.getConnection(toWindowHandle->getToken());
             toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
             synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
                                                            wallpaperFlags, traceTracker);
@@ -7315,4 +7221,132 @@
     }
 }
 
+InputDispatcher::ConnectionManager::ConnectionManager(const sp<android::Looper>& looper)
+      : mLooper(looper) {}
+
+// This destructor is required to ensure cleanup of each input connection, so that the fd is
+// removed from the looper.
+InputDispatcher::ConnectionManager::~ConnectionManager() {
+    while (!mConnectionsByToken.empty()) {
+        std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
+        removeConnection(connection);
+    }
+}
+
+void InputDispatcher::ConnectionManager::forEachGlobalMonitorConnection(
+        std::function<void(const std::shared_ptr<Connection>&)> f) const {
+    for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
+        for (const Monitor& monitor : monitors) {
+            f(monitor.connection);
+        }
+    }
+}
+
+void InputDispatcher::ConnectionManager::forEachGlobalMonitorConnection(
+        ui::LogicalDisplayId displayId,
+        std::function<void(const std::shared_ptr<Connection>&)> f) const {
+    auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
+    if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;
+
+    for (const Monitor& monitor : monitorsIt->second) {
+        f(monitor.connection);
+    }
+}
+
+void InputDispatcher::ConnectionManager::createGlobalInputMonitor(
+        ui::LogicalDisplayId displayId, std::unique_ptr<InputChannel>&& inputChannel,
+        const android::IdGenerator& idGenerator, gui::Pid pid, std::function<int(int)> callback) {
+    const int fd = inputChannel->getFd();
+    std::shared_ptr<Connection> connection =
+            std::make_shared<Connection>(std::move(inputChannel), /*monitor=*/true, idGenerator);
+    sp<IBinder> token = connection->getToken();
+    auto [_, inserted] = mConnectionsByToken.emplace(token, connection);
+    if (!inserted) {
+        ALOGE("Created a new connection, but the token %p is already known", token.get());
+    }
+    mGlobalMonitorsByDisplay[displayId].emplace_back(connection, pid);
+
+    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), nullptr);
+}
+
+void InputDispatcher::ConnectionManager::createConnection(
+        std::unique_ptr<InputChannel>&& inputChannel, const android::IdGenerator& idGenerator,
+        std::function<int(int)> callback) {
+    const int fd = inputChannel->getFd();
+    std::shared_ptr<Connection> connection =
+            std::make_shared<Connection>(std::move(inputChannel), /*monitor=*/false, idGenerator);
+    sp<IBinder> token = connection->getToken();
+    auto [_, inserted] = mConnectionsByToken.try_emplace(token, connection);
+    if (!inserted) {
+        ALOGE("Created a new connection, but the token %p is already known", token.get());
+    }
+
+    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback), nullptr);
+}
+
+status_t InputDispatcher::ConnectionManager::removeConnection(
+        const std::shared_ptr<Connection>& connection) {
+    mConnectionsByToken.erase(connection->getToken());
+
+    if (connection->monitor) {
+        removeMonitorChannel(connection->getToken());
+    }
+
+    mLooper->removeFd(connection->inputPublisher.getChannel().getFd());
+
+    connection->status = Connection::Status::ZOMBIE;
+    return OK;
+}
+
+std::string InputDispatcher::ConnectionManager::dump(nsecs_t currentTime) const {
+    std::string dump;
+    if (!mGlobalMonitorsByDisplay.empty()) {
+        for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
+            dump += StringPrintf("Global monitors on display %s:\n", displayId.toString().c_str());
+            const size_t numMonitors = monitors.size();
+            for (size_t i = 0; i < numMonitors; i++) {
+                const Monitor& monitor = monitors[i];
+                const std::shared_ptr<Connection>& connection = monitor.connection;
+                dump += StringPrintf(INDENT "%zu: '%s', ", i,
+                                     connection->getInputChannelName().c_str());
+                dump += "\n";
+            }
+        }
+    } else {
+        dump += "Global Monitors: <none>\n";
+    }
+
+    if (!mConnectionsByToken.empty()) {
+        dump += "Connections:\n";
+        for (const auto& [token, connection] : mConnectionsByToken) {
+            dump += StringPrintf(INDENT "%i: channelName='%s', "
+                                        "status=%s, monitor=%s, responsive=%s\n",
+                                 connection->inputPublisher.getChannel().getFd(),
+                                 connection->getInputChannelName().c_str(),
+                                 ftl::enum_string(connection->status).c_str(),
+                                 toString(connection->monitor), toString(connection->responsive));
+
+            if (!connection->outboundQueue.empty()) {
+                dump += StringPrintf(INDENT2 "OutboundQueue: length=%zu\n",
+                                     connection->outboundQueue.size());
+                dump += dumpQueue(connection->outboundQueue, currentTime);
+            }
+
+            if (!connection->waitQueue.empty()) {
+                dump += StringPrintf(INDENT2 "WaitQueue: length=%zu\n",
+                                     connection->waitQueue.size());
+                dump += dumpQueue(connection->waitQueue, currentTime);
+            }
+            std::string inputStateDump = streamableToString(connection->inputState);
+            if (!inputStateDump.empty()) {
+                dump += INDENT2 "InputState: ";
+                dump += inputStateDump + "\n";
+            }
+        }
+    } else {
+        dump += "Connections: <none>\n";
+    }
+    return dump;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index bca1c67..7bfa738 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -260,13 +260,6 @@
             const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay,
             ui::LogicalDisplayId displayId);
 
-    std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
-            REQUIRES(mLock);
-
-    std::string getConnectionNameLocked(const sp<IBinder>& connectionToken) const REQUIRES(mLock);
-
-    void removeConnectionLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
-
     status_t pilferPointersLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     template <typename T>
@@ -274,17 +267,6 @@
         std::size_t operator()(const sp<T>& b) const { return std::hash<T*>{}(b.get()); }
     };
 
-    // All registered connections mapped by input channel token.
-    std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
-            mConnectionsByToken GUARDED_BY(mLock);
-
-    // Find a monitor pid by the provided token.
-    std::optional<gui::Pid> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
-
-    // Input channels that will receive a copy of all input events sent to the provided display.
-    std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay
-            GUARDED_BY(mLock);
-
     const HmacKeyManager mHmacKeyManager;
     const std::array<uint8_t, 32> getSignature(const MotionEntry& motionEntry,
                                                const DispatchEntry& dispatchEntry) const;
@@ -417,6 +399,48 @@
 
     DispatcherWindowInfo mWindowInfos GUARDED_BY(mLock);
 
+    class ConnectionManager {
+    public:
+        ConnectionManager(const sp<Looper>& lopper);
+        ~ConnectionManager();
+
+        std::shared_ptr<Connection> getConnection(const sp<IBinder>& inputConnectionToken) const;
+
+        // Find a monitor pid by the provided token.
+        std::optional<gui::Pid> findMonitorPidByToken(const sp<IBinder>& token) const;
+        void forEachGlobalMonitorConnection(
+                std::function<void(const std::shared_ptr<Connection>&)> f) const;
+        void forEachGlobalMonitorConnection(
+                ui::LogicalDisplayId displayId,
+                std::function<void(const std::shared_ptr<Connection>&)> f) const;
+
+        void createGlobalInputMonitor(ui::LogicalDisplayId displayId,
+                                      std::unique_ptr<InputChannel>&& inputChannel,
+                                      const IdGenerator& idGenerator, gui::Pid pid,
+                                      std::function<int(int)> callback);
+
+        status_t removeConnection(const std::shared_ptr<Connection>& connection);
+
+        void createConnection(std::unique_ptr<InputChannel>&& inputChannel,
+                              const IdGenerator& idGenerator, std::function<int(int)> callback);
+
+        std::string dump(nsecs_t currentTime) const;
+
+    private:
+        const sp<Looper> mLooper;
+
+        // All registered connections mapped by input channel token.
+        std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
+                mConnectionsByToken;
+
+        // Input channels that will receive a copy of all input events sent to the provided display.
+        std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay;
+
+        void removeMonitorChannel(const sp<IBinder>& connectionToken);
+    };
+
+    ConnectionManager mConnectionManager GUARDED_BY(mLock);
+
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
             ui::LogicalDisplayId displayId) REQUIRES(mLock);
@@ -582,8 +606,6 @@
                                   nsecs_t& nextWakeupTime) REQUIRES(mLock);
     base::Result<std::vector<InputTarget>, android::os::InputEventInjectionResult>
     findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry& entry) REQUIRES(mLock);
-    std::vector<Monitor> selectResponsiveMonitorsLocked(
-            const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                InputTarget::DispatchMode dispatchMode,
@@ -689,12 +711,9 @@
 
     // Dump state.
     void dumpDispatchStateLocked(std::string& dump) const REQUIRES(mLock);
-    void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const;
     void logDispatchStateLocked() const REQUIRES(mLock);
     std::string dumpPointerCaptureStateLocked() const REQUIRES(mLock);
 
-    // Registration.
-    void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
     status_t removeInputChannelLocked(const std::shared_ptr<Connection>& connection, bool notify)
             REQUIRES(mLock);
 
diff --git a/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp
index 881a96b..4da05c1 100644
--- a/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp
@@ -133,10 +133,11 @@
 }
 
 void LatencyAggregatorWithHistograms::processStatistics(const InputEventTimeline& timeline) {
-    // Only gather data for Down, Move and Up motion events and Key events
+    // Only gather data for Down, Move, Up and Scroll motion events and Key events
     if (!(timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_DOWN ||
           timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_MOVE ||
           timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_UP ||
+          timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_SCROLL ||
           timeline.inputEventActionType == InputEventActionType::KEY))
         return;
 
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index 535c7ae..9039751 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -16,9 +16,7 @@
 
 #include "AndroidInputEventProtoConverter.h"
 
-#include <android/input.h>
 #include <android-base/logging.h>
-#include <input/Input.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
 
 namespace android::inputdispatcher::trace {
@@ -73,12 +71,6 @@
 
         const auto& coords = event.pointerCoords[i];
         auto bits = BitSet64(coords.bits);
-        if (isFromSource(event.source, AINPUT_SOURCE_CLASS_POINTER)) {
-            // Always include the X and Y axes for pointer events, since the
-            // bits will not be marked if the value is 0.
-            bits.markBit(AMOTION_EVENT_AXIS_X);
-            bits.markBit(AMOTION_EVENT_AXIS_Y);
-        }
         for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
             const auto axis = bits.clearFirstMarkedBit();
             auto axisEntry = pointer->add_axis_value();
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 24919b6..207806d 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -1085,7 +1085,7 @@
     return mReader->mEventHub.get();
 }
 
-int32_t InputReader::ContextImpl::getNextId() {
+int32_t InputReader::ContextImpl::getNextId() const {
     return mIdGenerator.nextId();
 }
 
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 1403ca2..931766b 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -154,7 +154,7 @@
                 REQUIRES(mReader->mLock) override;
         InputReaderPolicyInterface* getPolicy() REQUIRES(mReader->mLock) override;
         EventHubInterface* getEventHub() REQUIRES(mReader->mLock) override;
-        int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override;
+        int32_t getNextId() const NO_THREAD_SAFETY_ANALYSIS override;
         void updateLedMetaState(int32_t metaState) REQUIRES(mReader->mLock) override;
         int32_t getLedMetaState() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
         void setPreventingTouchpadTaps(bool prevent) REQUIRES(mReader->mLock)
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index e0e0ac2..20ed74f 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -55,7 +55,7 @@
     virtual InputReaderPolicyInterface* getPolicy() = 0;
     virtual EventHubInterface* getEventHub() = 0;
 
-    virtual int32_t getNextId() = 0;
+    virtual int32_t getNextId() const = 0;
 
     virtual void updateLedMetaState(int32_t metaState) = 0;
     virtual int32_t getLedMetaState() = 0;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 9f584a0..e21c2f9 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -419,7 +419,7 @@
     }
 }
 
-std::optional<ui::LogicalDisplayId> CursorInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> CursorInputMapper::getAssociatedDisplayId() const {
     return mDisplayId;
 }
 
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 773619c..301632f 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -63,7 +63,7 @@
 
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
 
-    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() const override;
 
 private:
     // Amount that trackball needs to move in order to generate a key event.
@@ -115,7 +115,6 @@
     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/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index d4a86ac..630c3d9 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -66,11 +66,12 @@
 
     virtual ~InputMapper();
 
-    inline int32_t getDeviceId() { return mDeviceContext.getId(); }
+    inline int32_t getDeviceId() const { return mDeviceContext.getId(); }
     inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
     inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; };
     inline const std::string getDeviceName() const { return mDeviceContext.getName(); }
     inline InputReaderContext* getContext() { return mDeviceContext.getContext(); }
+    inline const InputReaderContext* getContext() const { return mDeviceContext.getContext(); }
     inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); }
 
     virtual uint32_t getSources() const = 0;
@@ -114,7 +115,9 @@
 
     [[nodiscard]] virtual std::list<NotifyArgs> updateExternalStylusState(const StylusState& state);
 
-    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() { return std::nullopt; }
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() const {
+        return std::nullopt;
+    }
     virtual void updateLedState(bool reset) {}
 
     virtual std::optional<HardwareProperties> getTouchpadHardwareProperties();
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index fe3e4c2..400792b 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -104,14 +104,14 @@
     return mMapperSource;
 }
 
-ui::Rotation KeyboardInputMapper::getOrientation() {
+ui::Rotation KeyboardInputMapper::getOrientation() const {
     if (mViewport) {
         return mViewport->orientation;
     }
     return ui::ROTATION_0;
 }
 
-ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() {
+ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() const {
     if (mViewport) {
         return mViewport->displayId;
     }
@@ -471,7 +471,7 @@
     }
 }
 
-std::optional<ui::LogicalDisplayId> KeyboardInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> KeyboardInputMapper::getAssociatedDisplayId() const {
     if (mViewport) {
         return std::make_optional(mViewport->displayId);
     }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 7d9b3e4..9e2a81b 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -47,7 +47,7 @@
     int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override;
 
     int32_t getMetaState() override;
-    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() const override;
     void updateLedState(bool reset) override;
 
 private:
@@ -96,8 +96,8 @@
     void configureParameters();
     void dumpParameters(std::string& dump) const;
 
-    ui::Rotation getOrientation();
-    ui::LogicalDisplayId getDisplayId();
+    ui::Rotation getOrientation() const;
+    ui::LogicalDisplayId getDisplayId() const;
 
     [[nodiscard]] std::list<NotifyArgs> processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                    int32_t scanCode, int32_t usageCode);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 6efaeca..5cfda03 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3666,7 +3666,7 @@
         int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
         int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
         const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
-        float yPrecision, nsecs_t downTime, MotionClassification classification) {
+        float yPrecision, nsecs_t downTime, MotionClassification classification) const {
     std::vector<PointerCoords> pointerCoords;
     std::vector<PointerProperties> pointerProperties;
     uint32_t pointerCount = 0;
@@ -3992,7 +3992,7 @@
     return true;
 }
 
-std::optional<ui::LogicalDisplayId> TouchInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchInputMapper::getAssociatedDisplayId() const {
     return mParameters.hasAssociatedDisplay ? std::make_optional(mViewport.displayId)
                                             : std::nullopt;
 }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index eb4326f..96fc61b 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -185,7 +185,7 @@
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> updateExternalStylusState(
             const StylusState& state) override;
-    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() const override;
 
 protected:
     CursorButtonAccumulator mCursorButtonAccumulator;
@@ -840,7 +840,7 @@
             int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
             int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
             const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
-            float yPrecision, nsecs_t downTime, MotionClassification classification);
+            float yPrecision, nsecs_t downTime, MotionClassification classification) const;
 
     // Returns if this touch device is a touch screen with an associated display.
     bool isTouchScreen();
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 0df3364..18a7102 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -506,7 +506,7 @@
     return out;
 }
 
-std::optional<ui::LogicalDisplayId> TouchpadInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchpadInputMapper::getAssociatedDisplayId() const {
     return mDisplayId;
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 56553c9..9f53a7b 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -66,7 +66,7 @@
     using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
                                          uint16_t /*productId*/, uint16_t /*version*/>;
 
-    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() const override;
 
     std::optional<HardwareProperties> getTouchpadHardwareProperties() override;
 
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 827076a..480e276 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
+#include "../Macros.h"
+
 #include "gestures/GestureConverter.h"
 
+#include <ios>
 #include <optional>
 #include <sstream>
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
@@ -250,6 +254,18 @@
                                                             const Gesture& gesture) {
     std::list<NotifyArgs> out = {};
 
+    if (mCurrentClassification != MotionClassification::NONE) {
+        // Handling button changes during an ongoing gesture would be tricky, as we'd have to avoid
+        // sending duplicate DOWN events or premature UP events (e.g. if the gesture ended but the
+        // button was still down). It would also make handling touchpad events more difficult for
+        // apps, which would have to handle cases where e.g. a scroll gesture ends (and therefore
+        // the event lose the TWO_FINGER_SWIPE classification) but there isn't an UP because the
+        // button's still down. It's unclear how one should even handle button changes during most
+        // gestures, and they're probably accidental anyway. So, instead, just ignore them.
+        LOG(INFO) << "Ignoring button change because a gesture is ongoing.";
+        return out;
+    }
+
     PointerCoords coords;
     coords.clear();
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
@@ -312,6 +328,15 @@
     for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
         if (buttonsReleased & button) {
             uint32_t actionButton = gesturesButtonToMotionEventButton(button);
+            if (!(newButtonState & actionButton)) {
+                // We must have received the ButtonsChange gesture that put this button down during
+                // another gesture, and therefore dropped the BUTTON_PRESS action for it, or
+                // released the button when another gesture began during its press. Drop the
+                // BUTTON_RELEASE too to keep the stream consistent.
+                LOG(INFO) << "Dropping release event for button 0x" << std::hex << actionButton
+                          << " as it wasn't in the button state.";
+                continue;
+            }
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
@@ -362,7 +387,7 @@
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
-        out += exitHover(when, readTime);
+        out += prepareForFakeFingerGesture(when, readTime);
 
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
         coords.clear();
@@ -421,7 +446,7 @@
                     std::list<NotifyArgs> out;
                     mDownTime = when;
                     mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
-                    out += exitHover(when, readTime);
+                    out += prepareForFakeFingerGesture(when, readTime);
                     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                                  /*actionButton=*/0, /*buttonState=*/0,
                                                  /*pointerCount=*/1, &coords));
@@ -479,7 +504,7 @@
         // separate swipes with an appropriate lift event between them, so we don't have to worry
         // about the finger count changing mid-swipe.
 
-        out += exitHover(when, readTime);
+        out += prepareForFakeFingerGesture(when, readTime);
 
         mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
 
@@ -567,9 +592,7 @@
         LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
                             "First pinch gesture does not have the START zoom state (%d instead).",
                             gesture.details.pinch.zoom_state);
-        std::list<NotifyArgs> out;
-
-        out += exitHover(when, readTime);
+        std::list<NotifyArgs> out = prepareForFakeFingerGesture(when, readTime);
 
         mCurrentClassification = MotionClassification::PINCH;
         mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
@@ -644,6 +667,16 @@
     }
 }
 
+std::list<NotifyArgs> GestureConverter::prepareForFakeFingerGesture(nsecs_t when,
+                                                                    nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    if (isPointerDown(mButtonState)) {
+        out += releaseAllButtons(when, readTime);
+    }
+    out += exitHover(when, readTime);
+    return out;
+}
+
 NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action) {
     PointerCoords coords;
     coords.clear();
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 1f6acd3..ae85e3a 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -92,6 +92,8 @@
     [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime);
 
+    [[nodiscard]] std::list<NotifyArgs> prepareForFakeFingerGesture(nsecs_t when, nsecs_t readTime);
+
     NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action);
 
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 18e0b30..594ee3b 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -141,9 +141,9 @@
  */
 class CursorInputMapperUnitTestBase : public InputMapperUnitTest {
 protected:
-    void SetUp() override { SetUpWithBus(BUS_USB); }
-    void SetUpWithBus(int bus) override {
-        InputMapperUnitTest::SetUpWithBus(bus);
+    void SetUp() override { SetUp(BUS_USB, /*isExternal=*/false); }
+    void SetUp(int bus, bool isExternal) override {
+        InputMapperUnitTest::SetUp(bus, isExternal);
 
         // Current scan code state - all keys are UP by default
         setScanCodeState(KeyState::UP,
@@ -1142,7 +1142,9 @@
 
 class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
 protected:
-    void SetUp() override { SetUpWithBus(BUS_BLUETOOTH); }
+    void SetUp() override {
+        CursorInputMapperUnitTestBase::SetUp(BUS_BLUETOOTH, /*isExternal=*/true);
+    }
 };
 
 TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) {
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 8fa439d..fd9884b 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -15,11 +15,15 @@
  */
 
 #include <memory>
+#include <tuple>
 
+#include <android-base/result-gmock.h>
+#include <android-base/result.h>
 #include <com_android_input_flags.h>
 #include <flag_macros.h>
 #include <gestures/GestureConverter.h>
 #include <gtest/gtest.h>
+#include <input/InputVerifier.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
@@ -43,8 +47,12 @@
 const auto TOUCHPAD_PALM_REJECTION_V2 =
         ACONFIG_FLAG(input_flags, enable_v2_touchpad_typing_palm_rejection);
 
+constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
+constexpr stime_t GESTURE_TIME = ARBITRARY_GESTURE_TIME;
+
 } // namespace
 
+using android::base::testing::Ok;
 using testing::AllOf;
 using testing::Each;
 using testing::ElementsAre;
@@ -55,9 +63,8 @@
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
-    static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
 
-    void SetUp() {
+    GestureConverterTest() {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
         mFakeListener = std::make_unique<TestInputListener>();
@@ -1698,4 +1705,135 @@
                                     WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
 }
 
+/**
+ * Tests that the event stream output by the converter remains consistent when converting sequences
+ * of Gestures interleaved with button presses in various ways. Takes tuples of three Gestures: one
+ * that starts the gesture sequence, one that continues it (which may or may not be used in a
+ * particular test case), and one that ends it.
+ */
+class GestureConverterConsistencyTest
+      : public GestureConverterTest,
+        public testing::WithParamInterface<std::tuple<Gesture, Gesture, Gesture>> {
+protected:
+    GestureConverterConsistencyTest()
+          : GestureConverterTest(),
+            mParamStartGesture(std::get<0>(GetParam())),
+            mParamContinueGesture(std::get<1>(GetParam())),
+            mParamEndGesture(std::get<2>(GetParam())),
+            mDeviceContext(*mDevice, EVENTHUB_ID),
+            mConverter(*mReader->getContext(), mDeviceContext, DEVICE_ID),
+            mVerifier("Test verifier") {
+        mConverter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+    }
+
+    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);
+    }
+
+    void verifyArgsFromGesture(const Gesture& gesture, size_t gestureIndex) {
+        std::list<NotifyArgs> args =
+                mConverter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, gesture);
+        for (const NotifyArgs& notifyArg : args) {
+            const NotifyMotionArgs& arg = std::get<NotifyMotionArgs>(notifyArg);
+            ASSERT_THAT(processMotionArgs(arg), Ok())
+                    << "when processing " << arg.dump() << "\nfrom gesture " << gestureIndex << ": "
+                    << gesture.String();
+        }
+    }
+
+    void verifyArgsFromGestures(const std::vector<Gesture>& gestures) {
+        for (size_t i = 0; i < gestures.size(); i++) {
+            ASSERT_NO_FATAL_FAILURE(verifyArgsFromGesture(gestures[i], i));
+        }
+    }
+
+    Gesture mParamStartGesture;
+    Gesture mParamContinueGesture;
+    Gesture mParamEndGesture;
+
+    InputDeviceContext mDeviceContext;
+    GestureConverter mConverter;
+    InputVerifier mVerifier;
+};
+
+TEST_P(GestureConverterConsistencyTest, ButtonChangesDuringGesture) {
+    verifyArgsFromGestures({
+            mParamStartGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false),
+            mParamContinueGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_NONE, /*up=*/GESTURES_BUTTON_LEFT, /*is_tap=*/false),
+            mParamEndGesture,
+    });
+}
+
+TEST_P(GestureConverterConsistencyTest, ButtonDownDuringGestureAndUpAfterEnd) {
+    verifyArgsFromGestures({
+            mParamStartGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false),
+            mParamContinueGesture,
+            mParamEndGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_NONE, /*up=*/GESTURES_BUTTON_LEFT, /*is_tap=*/false),
+    });
+}
+
+TEST_P(GestureConverterConsistencyTest, GestureStartAndEndDuringButtonDown) {
+    verifyArgsFromGestures({
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false),
+            mParamStartGesture,
+            mParamContinueGesture,
+            mParamEndGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_NONE, /*up=*/GESTURES_BUTTON_LEFT, /*is_tap=*/false),
+    });
+}
+
+TEST_P(GestureConverterConsistencyTest, GestureStartsWhileButtonDownAndEndsAfterUp) {
+    verifyArgsFromGestures({
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false),
+            mParamStartGesture,
+            mParamContinueGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_NONE, /*up=*/GESTURES_BUTTON_LEFT, /*is_tap=*/false),
+            mParamEndGesture,
+    });
+}
+
+TEST_P(GestureConverterConsistencyTest, TapToClickDuringGesture) {
+    verifyArgsFromGestures({
+            mParamStartGesture,
+            Gesture(kGestureButtonsChange, GESTURE_TIME, GESTURE_TIME,
+                    /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_LEFT, /*is_tap=*/false),
+            mParamEndGesture,
+    });
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        GestureAndButtonInterleavings, GestureConverterConsistencyTest,
+        testing::Values(
+                std::make_tuple(Gesture(kGestureScroll, GESTURE_TIME, GESTURE_TIME, 0, -10),
+                                Gesture(kGestureScroll, GESTURE_TIME, GESTURE_TIME, 0, -5),
+                                Gesture(kGestureFling, GESTURE_TIME, GESTURE_TIME, 1, 1,
+                                        GESTURES_FLING_START)),
+                std::make_tuple(Gesture(kGestureSwipe, GESTURE_TIME, GESTURE_TIME, 0, -10),
+                                Gesture(kGestureSwipe, GESTURE_TIME, GESTURE_TIME, 0, 5),
+                                Gesture(kGestureSwipeLift, GESTURE_TIME, GESTURE_TIME)),
+                std::make_tuple(Gesture(kGestureFourFingerSwipe, GESTURE_TIME, GESTURE_TIME, 0,
+                                        -10),
+                                Gesture(kGestureFourFingerSwipe, GESTURE_TIME, GESTURE_TIME, 0, 5),
+                                Gesture(kGestureFourFingerSwipeLift, GESTURE_TIME, GESTURE_TIME)),
+                std::make_tuple(Gesture(kGesturePinch, GESTURE_TIME, GESTURE_TIME,
+                                        /*dz=*/1, GESTURES_ZOOM_START),
+                                Gesture(kGesturePinch, GESTURE_TIME, GESTURE_TIME,
+                                        /*dz=*/0.8, GESTURES_ZOOM_UPDATE),
+                                Gesture(kGesturePinch, GESTURE_TIME, GESTURE_TIME,
+                                        /*dz=*/1, GESTURES_ZOOM_END))));
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e8f32bf..6b4c4b7 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -12371,69 +12371,43 @@
     }
 
     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: {
-                PointerBuilder pointer = PointerBuilder(0, ToolType::STYLUS).x(50).y(50);
+            case AINPUT_SOURCE_STYLUS:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionEvent(*mDispatcher,
                                             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                                AINPUT_SOURCE_STYLUS)
                                                     .buttonState(
                                                             AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                                    .pointer(pointer)
+                                                    .pointer(PointerBuilder(0, ToolType::STYLUS)
+                                                                     .x(50)
+                                                                     .y(50))
                                                     .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: {
-                PointerBuilder pointer =
-                        PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(50).y(50);
+            case AINPUT_SOURCE_MOUSE:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionEvent(*mDispatcher,
                                             MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
                                                                AINPUT_SOURCE_MOUSE)
                                                     .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
-                                                    .pointer(pointer)
+                                                    .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                            ToolType::MOUSE)
+                                                                     .x(50)
+                                                                     .y(50))
                                                     .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);
     }
@@ -12633,16 +12607,6 @@
     // 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))
@@ -12884,18 +12848,6 @@
     // 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/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 8235c90..77f42f2 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -30,7 +30,7 @@
 using testing::Return;
 using testing::ReturnRef;
 
-void InputMapperUnitTest::SetUpWithBus(int bus) {
+void InputMapperUnitTest::SetUp(int bus, bool isExternal) {
     mFakePolicy = sp<FakeInputReaderPolicy>::make();
 
     EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
@@ -46,8 +46,9 @@
         return mPropertyMap;
     });
 
-    mDevice = std::make_unique<NiceMock<MockInputDevice>>(&mMockInputReaderContext, DEVICE_ID,
-                                                          /*generation=*/2, mIdentifier);
+    mDevice =
+            std::make_unique<NiceMock<MockInputDevice>>(&mMockInputReaderContext, DEVICE_ID,
+                                                        /*generation=*/2, mIdentifier, isExternal);
     ON_CALL((*mDevice), getConfiguration).WillByDefault(ReturnRef(mPropertyMap));
     mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
 }
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 10ef6f1..edc87a3 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -40,8 +40,8 @@
 protected:
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
-    virtual void SetUp() override { SetUpWithBus(0); }
-    virtual void SetUpWithBus(int bus);
+    virtual void SetUp() override { SetUp(/*bus=*/0, /*isExternal=*/false); }
+    virtual void SetUp(int bus, bool isExternal);
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution,
                    int32_t flat = 0, int32_t fuzz = 0);
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index ac616d0..dce5472 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -68,7 +68,7 @@
     MOCK_METHOD(InputReaderPolicyInterface*, getPolicy, (), (override));
     MOCK_METHOD(EventHubInterface*, getEventHub, (), (override));
 
-    int32_t getNextId() override { return 1; };
+    int32_t getNextId() const override { return 1; };
 
     MOCK_METHOD(void, updateLedMetaState, (int32_t metaState), (override));
     MOCK_METHOD(int32_t, getLedMetaState, (), (override));
@@ -196,14 +196,14 @@
 class MockInputDevice : public InputDevice {
 public:
     MockInputDevice(InputReaderContext* context, int32_t id, int32_t generation,
-                    const InputDeviceIdentifier& identifier)
-          : InputDevice(context, id, generation, identifier) {}
+                    const InputDeviceIdentifier& identifier, bool isExternal)
+          : InputDevice(context, id, generation, identifier), mIsExternal(isExternal) {}
 
     MOCK_METHOD(uint32_t, getSources, (), (const, override));
     MOCK_METHOD(std::optional<DisplayViewport>, getAssociatedViewport, (), (const));
     MOCK_METHOD(KeyboardType, getKeyboardType, (), (const, override));
     MOCK_METHOD(bool, isEnabled, (), ());
-    MOCK_METHOD(bool, isExternal, (), (override));
+    bool isExternal() override { return mIsExternal; }
 
     MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ());
     MOCK_METHOD(void, addEmptyEventHubDevice, (int32_t eventHubId), ());
@@ -266,5 +266,6 @@
 
 private:
     int32_t mGeneration = 0;
+    const bool mIsExternal;
 };
 } // namespace android
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 1dd32c4..17acdd4 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -1085,10 +1085,9 @@
 class KeyboardInputMapperTest_ExternalAlphabeticDevice : public KeyboardInputMapperUnitTest {
 protected:
     void SetUp() override {
-        InputMapperUnitTest::SetUp();
+        InputMapperUnitTest::SetUp(/*bus=*/0, /*isExternal=*/true);
         ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD));
         ON_CALL((*mDevice), getKeyboardType).WillByDefault(Return(KeyboardType::ALPHABETIC));
-        ON_CALL((*mDevice), isExternal).WillByDefault(Return(true));
         EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
                 .WillRepeatedly(Return(InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY |
                                        InputDeviceClass::EXTERNAL));
@@ -1102,10 +1101,9 @@
 class KeyboardInputMapperTest_ExternalNonAlphabeticDevice : public KeyboardInputMapperUnitTest {
 protected:
     void SetUp() override {
-        InputMapperUnitTest::SetUp();
+        InputMapperUnitTest::SetUp(/*bus=*/0, /*isExternal=*/true);
         ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD));
         ON_CALL((*mDevice), getKeyboardType).WillByDefault(Return(KeyboardType::NON_ALPHABETIC));
-        ON_CALL((*mDevice), isExternal).WillByDefault(Return(true));
         EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
                 .WillRepeatedly(Return(InputDeviceClass::KEYBOARD | InputDeviceClass::EXTERNAL));
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index ca0f1e8..1cfaaa8 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -57,6 +57,7 @@
 }
 
 const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
+const auto FIRST_MOUSE_POINTER = PointerBuilder(/*id=*/1, ToolType::MOUSE);
 
 /**
  * This is a convenience method for comparing timelines that also prints the difference between
@@ -491,8 +492,13 @@
             /*vendorId*/ 0, /*productId*/ 0, {InputDeviceUsageSource::BUTTONS},
             InputEventActionType::KEY);
 
-    InputEventTimeline unknownTimeline(
+    InputEventTimeline motionScrollTimeline(
             /*eventTime*/ 12, /*readTime*/ 13,
+            /*vendorId*/ 0, /*productId*/ 0, {InputDeviceUsageSource::MOUSE},
+            InputEventActionType::MOTION_ACTION_SCROLL);
+
+    InputEventTimeline unknownTimeline(
+            /*eventTime*/ 14, /*readTime*/ 15,
             /*vendorId*/ 0, /*productId*/ 0, {InputDeviceUsageSource::TOUCHSCREEN},
             InputEventActionType::UNKNOWN_INPUT_EVENT);
 
@@ -529,8 +535,15 @@
                     .readTime(keyUpTimeline.readTime)
                     .deviceId(DEVICE_ID)
                     .build());
+    mTracker->trackListener(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL, AINPUT_SOURCE_MOUSE, inputEventId + 5)
+                    .eventTime(motionScrollTimeline.eventTime)
+                    .readTime(motionScrollTimeline.readTime)
+                    .deviceId(DEVICE_ID)
+                    .pointer(FIRST_MOUSE_POINTER)
+                    .build());
     mTracker->trackListener(MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN,
-                                              AINPUT_SOURCE_TOUCHSCREEN, inputEventId + 5)
+                                              AINPUT_SOURCE_TOUCHSCREEN, inputEventId + 6)
                                     .eventTime(unknownTimeline.eventTime)
                                     .readTime(unknownTimeline.readTime)
                                     .deviceId(DEVICE_ID)
@@ -541,7 +554,8 @@
 
     std::vector<InputEventTimeline> expectedTimelines = {motionDownTimeline, motionMoveTimeline,
                                                          motionUpTimeline,   keyDownTimeline,
-                                                         keyUpTimeline,      unknownTimeline};
+                                                         keyUpTimeline,      motionScrollTimeline,
+                                                         unknownTimeline};
     assertReceivedTimelines(expectedTimelines);
 }
 
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index d15048d..0ea22d4 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -53,8 +53,9 @@
  */
 class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
 protected:
-    void SetUp() override {
-        InputMapperUnitTest::SetUp();
+    void SetUp() override { SetUp(/*bus=*/0, /*isExternal=*/false); }
+    void SetUp(int bus, bool isExternal) override {
+        InputMapperUnitTest::SetUp(bus, isExternal);
 
         // Present scan codes
         expectScanCodes(/*present=*/true,
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
index 157bee3..548df22 100644
--- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -89,9 +89,9 @@
  */
 class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
 protected:
-    void SetUp() override { SetUpWithBus(BUS_USB); }
-    void SetUpWithBus(int bus) override {
-        InputMapperUnitTest::SetUpWithBus(bus);
+    void SetUp() override { SetUp(/*bus=*/0, /*isExternal=*/false); }
+    void SetUp(int bus, bool isExternal) override {
+        InputMapperUnitTest::SetUp(bus, isExternal);
 
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
                 .WillRepeatedly(Return(true));
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index abce931..31db2fe 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, args.actionButton,
+                verifier.processMovement(args.deviceId, args.source, args.action,
                                          args.getPointerCount(), args.pointerProperties.data(),
-                                         args.pointerCoords.data(), args.flags, args.buttonState);
+                                         args.pointerCoords.data(), args.flags);
         if (result.ok()) {
             return args;
         }
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index a1da39a..846260a 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -346,7 +346,7 @@
     }
     InputReaderPolicyInterface* getPolicy() override { return mPolicy.get(); }
     EventHubInterface* getEventHub() override { return mEventHub.get(); }
-    int32_t getNextId() override { return mFdp->ConsumeIntegral<int32_t>(); }
+    int32_t getNextId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
 
     void updateLedMetaState(int32_t metaState) override{};
     int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 05f7d1b..ea7d6d7 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -26,15 +26,15 @@
 cc_defaults {
     name: "surfaceflinger_defaults",
     cflags: [
+        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
         "-Wall",
+        "-Wconversion",
         "-Werror",
         "-Wextra",
         "-Wformat",
         "-Wthread-safety",
-        "-Wunused",
         "-Wunreachable-code",
-        "-Wconversion",
-        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+        "-Wunused",
     ],
 }
 
@@ -43,39 +43,41 @@
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
         "librenderengine_deps",
-        "libtimestats_deps",
         "libsurfaceflinger_common_deps",
-        "surfaceflinger_defaults",
         "libsurfaceflinger_proto_deps",
+        "libtimestats_deps",
         "poweradvisor_deps",
+        "surfaceflinger_defaults",
     ],
     cflags: [
-        "-DLOG_TAG=\"SurfaceFlinger\"",
-        "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DLOG_TAG=\"SurfaceFlinger\"",
     ],
     shared_libs: [
+        "android.hardware.common-V2-ndk",
+        "android.hardware.common.fmq-V1-ndk",
         "android.hardware.configstore-utils",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore@1.1",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.common@1.2",
-        "android.hardware.common-V2-ndk",
-        "android.hardware.common.fmq-V1-ndk",
         "android.hardware.graphics.composer@2.1",
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
         "android.os.flags-aconfig-cc-host",
+        "libEGL",
+        "libGLESv1_CM",
+        "libGLESv2",
+        "libSurfaceFlingerProp",
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libbinder_ndk",
         "libcutils",
-        "libEGL",
         "libfmq",
-        "libGLESv1_CM",
-        "libGLESv2",
         "libgui",
         "libhidlbase",
         "liblog",
@@ -86,8 +88,6 @@
         "libsync",
         "libui",
         "libutils",
-        "libSurfaceFlingerProp",
-        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "iinputflinger_aidl_lib_static",
@@ -105,11 +105,11 @@
         "libtonemap",
     ],
     header_libs: [
+        "android.hardware.graphics.composer3-command-buffer",
         "android.hardware.graphics.composer@2.1-command-buffer",
         "android.hardware.graphics.composer@2.2-command-buffer",
         "android.hardware.graphics.composer@2.3-command-buffer",
         "android.hardware.graphics.composer@2.4-command-buffer",
-        "android.hardware.graphics.composer3-command-buffer",
     ],
     export_static_lib_headers: [
         "libcompositionengine",
@@ -125,8 +125,8 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
-        "libpowermanager",
         "libhidlbase",
+        "libpowermanager",
     ],
     // TODO (marissaw): this library is not used by surfaceflinger. This is here so
     // the library compiled in a way that is accessible to system partition when running
@@ -177,7 +177,6 @@
 filegroup {
     name: "libsurfaceflinger_backend_sources",
     srcs: [
-        "PowerAdvisor/*.cpp",
         "DisplayHardware/AidlComposerHal.cpp",
         "DisplayHardware/ComposerHal.cpp",
         "DisplayHardware/FramebufferSurface.cpp",
@@ -185,6 +184,7 @@
         "DisplayHardware/HWComposer.cpp",
         "DisplayHardware/HidlComposerHal.cpp",
         "DisplayHardware/VirtualDisplaySurface.cpp",
+        "PowerAdvisor/*.cpp",
     ],
 }
 
@@ -208,21 +208,20 @@
         "DisplayDevice.cpp",
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
-        "FrontEnd/LayerCreationArgs.cpp",
-        "FrontEnd/LayerHandle.cpp",
-        "FrontEnd/LayerSnapshot.cpp",
-        "FrontEnd/LayerSnapshotBuilder.cpp",
-        "FrontEnd/LayerHierarchy.cpp",
-        "FrontEnd/LayerLifecycleManager.cpp",
-        "FrontEnd/RequestedLayerState.cpp",
-        "FrontEnd/TransactionHandler.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
+        "FrontEnd/LayerCreationArgs.cpp",
+        "FrontEnd/LayerHandle.cpp",
+        "FrontEnd/LayerHierarchy.cpp",
+        "FrontEnd/LayerLifecycleManager.cpp",
+        "FrontEnd/LayerSnapshot.cpp",
+        "FrontEnd/LayerSnapshotBuilder.cpp",
+        "FrontEnd/RequestedLayerState.cpp",
+        "FrontEnd/TransactionHandler.cpp",
         "HdrLayerInfoReporter.cpp",
         "HdrSdrRatioOverlay.cpp",
         "Jank/JankTracker.cpp",
-        "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
         "LayerProtoHelper.cpp",
@@ -234,10 +233,10 @@
         "RenderArea.cpp",
         "Scheduler/EventThread.cpp",
         "Scheduler/FrameRateOverrideMappings.cpp",
-        "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
         "Scheduler/LayerInfo.cpp",
         "Scheduler/MessageQueue.cpp",
+        "Scheduler/OneShotTimer.cpp",
         "Scheduler/RefreshRateSelector.cpp",
         "Scheduler/Scheduler.cpp",
         "Scheduler/SmallAreaDetectionAllowMappings.cpp",
@@ -253,19 +252,20 @@
         "Tracing/LayerDataSource.cpp",
         "Tracing/LayerTracing.cpp",
         "Tracing/TransactionDataSource.cpp",
-        "Tracing/TransactionTracing.cpp",
         "Tracing/TransactionProtoParser.cpp",
+        "Tracing/TransactionTracing.cpp",
         "Tracing/tools/LayerTraceGenerator.cpp",
         "TransactionCallbackInvoker.cpp",
         "TunnelModeEnabledReporter.cpp",
+        "WindowInfosListenerInvoker.cpp",
     ],
 }
 
 cc_defaults {
     name: "libsurfaceflinger_binary",
     defaults: [
-        "surfaceflinger_defaults",
         "libsurfaceflinger_production_defaults",
+        "surfaceflinger_defaults",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
@@ -331,9 +331,9 @@
         "android.hardware.configstore@1.1",
         "android.hardware.graphics.common@1.2",
         "libhidlbase",
+        "liblog",
         "libui",
         "libutils",
-        "liblog",
     ],
     static_libs: [
         "libSurfaceFlingerProperties",
@@ -354,10 +354,10 @@
     generated_headers: ["statslog_surfaceflinger.h"],
     export_generated_headers: ["statslog_surfaceflinger.h"],
     shared_libs: [
+        "android.os.statsbootstrap_aidl-cpp",
         "libbinder",
         "libstatsbootstrap",
         "libutils",
-        "android.os.statsbootstrap_aidl-cpp",
     ],
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 7f94428..c47943f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -138,13 +138,13 @@
 }
 
 std::optional<DisplayIdentificationInfo> HWComposer::onHotplug(hal::HWDisplayId hwcDisplayId,
-                                                               hal::Connection connection) {
-    switch (connection) {
-        case hal::Connection::CONNECTED:
+                                                               HotplugEvent event) {
+    switch (event) {
+        case HotplugEvent::Connected:
             return onHotplugConnect(hwcDisplayId);
-        case hal::Connection::DISCONNECTED:
+        case HotplugEvent::Disconnected:
             return onHotplugDisconnect(hwcDisplayId);
-        case hal::Connection::INVALID:
+        case HotplugEvent::LinkUnstable:
             return {};
     }
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index b1b997a..d60f6ff 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -231,11 +231,12 @@
 
     // Events handling ---------------------------------------------------------
 
-    // Returns stable display ID (and display name on connection of new or previously disconnected
-    // display), or std::nullopt if hotplug event was ignored.
+    enum class HotplugEvent { Connected, Disconnected, LinkUnstable };
+
+    // Returns the stable display ID of the display for which the hotplug event was received, or
+    // std::nullopt if hotplug event was ignored.
     // This function is called from SurfaceFlinger.
-    virtual std::optional<DisplayIdentificationInfo> onHotplug(hal::HWDisplayId,
-                                                               hal::Connection) = 0;
+    virtual std::optional<DisplayIdentificationInfo> onHotplug(hal::HWDisplayId, HotplugEvent) = 0;
 
     // If true we'll update the DeviceProductInfo on subsequent hotplug connected events.
     // TODO(b/157555476): Remove when the framework has proper support for headless mode
@@ -435,9 +436,7 @@
 
     // Events handling ---------------------------------------------------------
 
-    // Returns PhysicalDisplayId (and display name on connection of new or previously disconnected
-    // display), or std::nullopt if hotplug event was ignored.
-    std::optional<DisplayIdentificationInfo> onHotplug(hal::HWDisplayId, hal::Connection) override;
+    std::optional<DisplayIdentificationInfo> onHotplug(hal::HWDisplayId, HotplugEvent) override;
 
     bool updatesDeviceProductInfoOnHotplugReconnect() const override;
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 86d7388..fece312 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -611,7 +611,11 @@
         mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
     }
 
-    if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
+    const nsecs_t presentThreshold =
+            FlagManager::getInstance().increase_missed_frame_jank_threshold()
+            ? mJankClassificationThresholds.presentThresholdExtended
+            : mJankClassificationThresholds.presentThresholdLegacy;
+    if (std::abs(presentDelta) > presentThreshold) {
         mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
                                                  : FramePresentMetadata::EarlyPresent;
         // Jank that is missing by less than the render rate period is classified as partial jank,
@@ -629,9 +633,8 @@
     } else if (mFramePresentMetadata == FramePresentMetadata::EarlyPresent) {
         if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
             // Finish on time, Present early
-            if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                deltaToVsync >= refreshRate.getPeriodNsecs() -
-                                mJankClassificationThresholds.presentThreshold) {
+            if (deltaToVsync < presentThreshold ||
+                deltaToVsync >= refreshRate.getPeriodNsecs() - presentThreshold) {
                 // Delta factor of vsync
                 mJankType = JankType::SurfaceFlingerScheduling;
             } else {
@@ -667,9 +670,8 @@
                 if (!(mJankType & JankType::BufferStuffing)) {
                     // In a stuffed state, if the app finishes on time and there is no display frame
                     // jank, only buffer stuffing is the root cause of the jank.
-                    if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                        deltaToVsync >= refreshRate.getPeriodNsecs() -
-                                        mJankClassificationThresholds.presentThreshold) {
+                    if (deltaToVsync < presentThreshold ||
+                        deltaToVsync >= refreshRate.getPeriodNsecs() - presentThreshold) {
                         // Delta factor of vsync
                         mJankType |= JankType::SurfaceFlingerScheduling;
                     } else {
@@ -1091,7 +1093,11 @@
             ? std::abs(presentDelta) % mRefreshRate.getPeriodNsecs()
             : 0;
 
-    if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
+    nsecs_t presentThreshold = FlagManager::getInstance().increase_missed_frame_jank_threshold()
+            ? mJankClassificationThresholds.presentThresholdExtended
+            : mJankClassificationThresholds.presentThresholdLegacy;
+
+    if (std::abs(presentDelta) > presentThreshold) {
         mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
                                                  : FramePresentMetadata::EarlyPresent;
         // Jank that is missing by less than the render rate period is classified as partial jank,
@@ -1122,9 +1128,8 @@
         if (mFramePresentMetadata == FramePresentMetadata::EarlyPresent) {
             if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
                 // Finish on time, Present early
-                if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                    deltaToVsync >= (mRefreshRate.getPeriodNsecs() -
-                                     mJankClassificationThresholds.presentThreshold)) {
+                if (deltaToVsync < presentThreshold ||
+                    deltaToVsync >= (mRefreshRate.getPeriodNsecs() - presentThreshold)) {
                     // Delta is a factor of vsync if its within the presentTheshold on either side
                     // of the vsyncPeriod. Example: 0-2ms and 9-11ms are both within the threshold
                     // of the vsyncPeriod if the threshold was 2ms and the vsyncPeriod was 11ms.
@@ -1142,7 +1147,7 @@
             }
         } else if (mFramePresentMetadata == FramePresentMetadata::LatePresent) {
             if (std::abs(mSurfaceFlingerPredictions.presentTime - previousPresentTime) <=
-                        mJankClassificationThresholds.presentThreshold ||
+                        presentThreshold ||
                 previousPresentTime > mSurfaceFlingerPredictions.presentTime) {
                 // The previous frame was either presented in the current frame's expected vsync or
                 // it was presented even later than the current frame's expected vsync.
@@ -1151,9 +1156,8 @@
             if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish &&
                 !(mJankType & JankType::SurfaceFlingerStuffing)) {
                 // Finish on time, Present late
-                if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                    deltaToVsync >= (mRefreshRate.getPeriodNsecs() -
-                                     mJankClassificationThresholds.presentThreshold)) {
+                if (deltaToVsync < presentThreshold ||
+                    deltaToVsync >= (mRefreshRate.getPeriodNsecs() - presentThreshold)) {
                     // Delta is a factor of vsync if its within the presentTheshold on either side
                     // of the vsyncPeriod. Example: 0-2ms and 9-11ms are both within the threshold
                     // of the vsyncPeriod if the threshold was 2ms and the vsyncPeriod was 11ms.
@@ -1165,8 +1169,7 @@
             } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
                 if (!(mJankType & JankType::SurfaceFlingerStuffing) ||
                     mSurfaceFlingerActuals.presentTime - previousPresentTime >
-                            mRefreshRate.getPeriodNsecs() +
-                                    mJankClassificationThresholds.presentThreshold) {
+                            mRefreshRate.getPeriodNsecs() + presentThreshold) {
                     // Classify CPU vs GPU if SF wasn't stuffed or if SF was stuffed but this frame
                     // was presented more than a vsync late.
                     if (mGpuFence != FenceTime::NO_FENCE) {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index a47bd57..9fedb57 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -107,7 +107,10 @@
 struct JankClassificationThresholds {
     // The various thresholds for App and SF. If the actual timestamp falls within the threshold
     // compared to prediction, we treat it as on time.
-    nsecs_t presentThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+    nsecs_t presentThresholdLegacy =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+    nsecs_t presentThresholdExtended =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(4ms).count();
     nsecs_t deadlineThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(0ms).count();
     nsecs_t startThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
 };
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 9119247..86ef6ca 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1178,7 +1178,7 @@
     auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
 
     if (!requested.hasInputInfo()) {
-        snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
+        snapshot.inputInfo.inputConfig |= InputConfig::NO_INPUT_CHANNEL;
     }
     fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 591ebb2..1d53e71 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -573,7 +573,7 @@
         return false;
     }
 
-    if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+    if (hasBufferOrSidebandStream() || fillsColor()) {
         return true;
     }
 
@@ -586,6 +586,15 @@
             windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
 }
 
+bool RequestedLayerState::hasBufferOrSidebandStream() const {
+    return ((sidebandStream != nullptr) || (externalTexture != nullptr));
+}
+
+bool RequestedLayerState::fillsColor() const {
+    return !hasBufferOrSidebandStream() && color.r >= 0.0_hf && color.g >= 0.0_hf &&
+            color.b >= 0.0_hf;
+}
+
 bool RequestedLayerState::hasBlur() const {
     return backgroundBlurRadius > 0 || blurRegions.size() > 0;
 }
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index dd861a7..7232379 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -88,6 +88,8 @@
     bool hasValidRelativeParent() const;
     bool hasInputInfo() const;
     bool needsInputInfo() const;
+    bool hasBufferOrSidebandStream() const;
+    bool fillsColor() const;
     bool hasBlur() const;
     bool hasFrameUpdate() const;
     bool hasReadyFrame() const;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1038b9f..e1c5383 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -135,7 +135,6 @@
 #include "DisplayDevice.h"
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/FramebufferSurface.h"
-#include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
@@ -2292,12 +2291,12 @@
 void SurfaceFlinger::onComposerHalHotplugEvent(hal::HWDisplayId hwcDisplayId,
                                                DisplayHotplugEvent event) {
     if (event == DisplayHotplugEvent::CONNECTED || event == DisplayHotplugEvent::DISCONNECTED) {
-        hal::Connection connection = (event == DisplayHotplugEvent::CONNECTED)
-                ? hal::Connection::CONNECTED
-                : hal::Connection::DISCONNECTED;
+        const HWComposer::HotplugEvent hotplugEvent = event == DisplayHotplugEvent::CONNECTED
+                ? HWComposer::HotplugEvent::Connected
+                : HWComposer::HotplugEvent::Disconnected;
         {
             std::lock_guard<std::mutex> lock(mHotplugMutex);
-            mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection});
+            mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, hotplugEvent});
         }
 
         if (mScheduler) {
@@ -3658,13 +3657,13 @@
         events = std::move(mPendingHotplugEvents);
     }
 
-    for (const auto [hwcDisplayId, connection] : events) {
-        if (auto info = getHwComposer().onHotplug(hwcDisplayId, connection)) {
+    for (const auto [hwcDisplayId, event] : events) {
+        if (auto info = getHwComposer().onHotplug(hwcDisplayId, event)) {
             const auto displayId = info->id;
             const ftl::Concat displayString("display ", displayId.value, "(HAL ID ", hwcDisplayId,
                                             ')');
 
-            if (connection == hal::Connection::CONNECTED) {
+            if (event == HWComposer::HotplugEvent::Connected) {
                 const auto activeModeIdOpt =
                         processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
                                               displayString.c_str());
@@ -3975,9 +3974,6 @@
                                                  displaySurface, producer);
 
     if (mScheduler && !display->isVirtual()) {
-        // TODO(b/241285876): Annotate `processDisplayAdded` instead.
-        ftl::FakeGuard guard(kMainThreadContext);
-
         // For hotplug reconnect, renew the registration since display modes have been reloaded.
         mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
                                     mActiveDisplayId);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 158bf57..a99b39a 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -75,6 +75,7 @@
 #include "Display/VirtualDisplaySnapshot.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/HWComposer.h"
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
 #include "FrontEnd/DisplayInfo.h"
@@ -126,7 +127,6 @@
 class FpsReporter;
 class TunnelModeEnabledReporter;
 class HdrLayerInfoReporter;
-class HWComposer;
 class IGraphicBufferProducer;
 class Layer;
 class MessageBase;
@@ -350,9 +350,6 @@
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();
 
-    virtual void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState&)
-            REQUIRES(mStateLock);
-
     virtual std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
             BufferData& bufferData, const char* layerName, uint64_t transactionId);
 
@@ -1042,6 +1039,8 @@
             const sp<compositionengine::DisplaySurface>& displaySurface,
             const sp<IGraphicBufferProducer>& producer) REQUIRES(mStateLock);
     void processDisplayChangesLocked() REQUIRES(mStateLock, kMainThreadContext);
+    void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState&)
+            REQUIRES(mStateLock, kMainThreadContext);
     void processDisplayRemoved(const wp<IBinder>& displayToken)
             REQUIRES(mStateLock, kMainThreadContext);
     void processDisplayChanged(const wp<IBinder>& displayToken,
@@ -1279,7 +1278,7 @@
 
     struct HotplugEvent {
         hal::HWDisplayId hwcDisplayId;
-        hal::Connection connection = hal::Connection::INVALID;
+        HWComposer::HotplugEvent event;
     };
 
     bool mIsHdcpViaNegVsync = false;
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 15df152..b1552e6 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -126,6 +126,7 @@
     DUMP_ACONFIG_FLAG(adpf_native_session_manager);
     DUMP_ACONFIG_FLAG(adpf_use_fmq_channel);
     DUMP_ACONFIG_FLAG(graphite_renderengine_preview_rollout);
+    DUMP_ACONFIG_FLAG(increase_missed_frame_jank_threshold);
     DUMP_ACONFIG_FLAG(refresh_rate_overlay_on_external_display);
 
     /// Trunk stable readonly flags ///
@@ -300,6 +301,7 @@
 FLAG_MANAGER_ACONFIG_FLAG(adpf_gpu_sf, "")
 FLAG_MANAGER_ACONFIG_FLAG(adpf_native_session_manager, "");
 FLAG_MANAGER_ACONFIG_FLAG(graphite_renderengine_preview_rollout, "");
+FLAG_MANAGER_ACONFIG_FLAG(increase_missed_frame_jank_threshold, "");
 
 /// Trunk stable server (R/W) flags from outside SurfaceFlinger ///
 FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 147e79e..073302e 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -61,6 +61,7 @@
     bool adpf_use_fmq_channel() const;
     bool adpf_use_fmq_channel_fixed() const;
     bool graphite_renderengine_preview_rollout() const;
+    bool increase_missed_frame_jank_threshold() const;
     bool refresh_rate_overlay_on_external_display() const;
 
     /// Trunk stable readonly flags ///
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index b28d269..96ab7ab 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -190,6 +190,13 @@
 } # graphite_renderengine_preview_rollout
 
 flag {
+  name: "increase_missed_frame_jank_threshold"
+  namespace: "core_graphics"
+  description: "Increase the jank threshold to 4 milliseconds"
+  bug: "342265411"
+} # increase_missed_frame_jank_threshold
+
+flag {
   name: "latch_unsignaled_with_auto_refresh_changed"
   namespace: "core_graphics"
   description: "Ignore eAutoRefreshChanged with latch unsignaled"
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
index c6cbe52..84dc5fc 100644
--- a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -67,7 +67,8 @@
                     setVsyncEnabled(kHwcDisplayId, hal::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mComposerHal, onHotplugConnect(kHwcDisplayId));
 
-        const auto infoOpt = mComposer->onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+        const auto infoOpt =
+                mComposer->onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
         ASSERT_TRUE(infoOpt);
 
         mDisplayId = infoOpt->id;
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 6e231aa..3fead93 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -360,9 +360,10 @@
     // The HWC active configuration id
     static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001;
 
-    static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) {
+    static void injectPendingHotplugEvent(DisplayTransactionTest* test,
+                                          HWComposer::HotplugEvent event) {
         test->mFlinger.mutablePendingHotplugEvents().emplace_back(
-                TestableSurfaceFlinger::HotplugEvent{HWC_DISPLAY_ID, connection});
+                TestableSurfaceFlinger::HotplugEvent{HWC_DISPLAY_ID, event});
     }
 
     // Called by tests to inject a HWC display setup
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 08e4265..54f2259 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -202,10 +202,12 @@
     uint32_t* maxDisplayFrames;
     size_t maxTokens;
     static constexpr pid_t kSurfaceFlingerPid = 666;
-    static constexpr nsecs_t kPresentThreshold = std::chrono::nanoseconds(2ns).count();
+    static constexpr nsecs_t kPresentThresholdLegacy = std::chrono::nanoseconds(2ns).count();
+    static constexpr nsecs_t kPresentThresholdExtended = std::chrono::nanoseconds(4ns).count();
     static constexpr nsecs_t kDeadlineThreshold = std::chrono::nanoseconds(0ns).count();
     static constexpr nsecs_t kStartThreshold = std::chrono::nanoseconds(2ns).count();
-    static constexpr JankClassificationThresholds kTestThresholds{kPresentThreshold,
+    static constexpr JankClassificationThresholds kTestThresholds{kPresentThresholdLegacy,
+                                                                  kPresentThresholdExtended,
                                                                   kDeadlineThreshold,
                                                                   kStartThreshold};
 };
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index ba2d3e2..b34de1a 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -94,7 +94,7 @@
     constexpr hal::HWDisplayId kHwcDisplayId = 1;
     expectHotplugConnect(kHwcDisplayId);
 
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     ASSERT_FALSE(mHwc.isHeadless());
@@ -111,7 +111,7 @@
     constexpr hal::HWDisplayId kHwcDisplayId = 1;
     expectHotplugConnect(kHwcDisplayId);
 
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     EXPECT_CALL(*mHal, getDisplayConnectionType(kHwcDisplayId, _))
@@ -133,7 +133,7 @@
     constexpr hal::HWDisplayId kHwcDisplayId = 2;
     expectHotplugConnect(kHwcDisplayId);
 
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     {
@@ -164,7 +164,7 @@
     constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
 
     expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
     ASSERT_TRUE(info->preferredDetailedTimingDescriptor.has_value());
 
@@ -266,7 +266,7 @@
     constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
 
     expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
@@ -364,7 +364,7 @@
     constexpr hal::HWConfigId kConfigId = 42;
     constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
     expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(true));
@@ -452,7 +452,7 @@
     constexpr hal::HWDisplayId kHwcDisplayId = 1;
     expectHotplugConnect(kHwcDisplayId);
 
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, HWComposer::HotplugEvent::Connected);
     ASSERT_TRUE(info);
 
     const auto physicalDisplayId = info->id;
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 976cecb..35ec536 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -619,14 +619,32 @@
     }
 }
 
-TEST_F(LayerLifecycleManagerTest, testInputInfoOfRequestedLayerState) {
-    // By default the layer has no buffer, so it doesn't need an input info
-    EXPECT_FALSE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
-
-    setBuffer(111);
+TEST_F(LayerLifecycleManagerTest, layerWithBufferNeedsInputInfo) {
+    // If a layer has no buffer or no color, it doesn't have an input info
+    LayerHierarchyTestBase::createRootLayer(3);
+    setColor(3, {-1._hf, -1._hf, -1._hf});
     mLifecycleManager.commitChanges();
 
-    EXPECT_TRUE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
+    EXPECT_FALSE(getRequestedLayerState(mLifecycleManager, 3)->needsInputInfo());
+
+    setBuffer(3);
+    mLifecycleManager.commitChanges();
+
+    EXPECT_TRUE(getRequestedLayerState(mLifecycleManager, 3)->needsInputInfo());
+}
+
+TEST_F(LayerLifecycleManagerTest, layerWithColorNeedsInputInfo) {
+    // If a layer has no buffer or no color, it doesn't have an input info
+    LayerHierarchyTestBase::createRootLayer(4);
+    setColor(4, {-1._hf, -1._hf, -1._hf});
+    mLifecycleManager.commitChanges();
+
+    EXPECT_FALSE(getRequestedLayerState(mLifecycleManager, 4)->needsInputInfo());
+
+    setColor(4, {1._hf, 0._hf, 0._hf});
+    mLifecycleManager.commitChanges();
+
+    EXPECT_TRUE(getRequestedLayerState(mLifecycleManager, 4)->needsInputInfo());
 }
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index ab8c733..453c053 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -2045,17 +2045,17 @@
 }
 
 TEST_F(LayerSnapshotTest, shouldUpdateInputWhenNoInputInfo) {
-    // By default the layer has no buffer, so we don't expect it to have an input info
+    // If a layer has no buffer or no color, it doesn't have an input info
+    setColor(111, {-1._hf, -1._hf, -1._hf});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 12, 121, 122, 1221, 13, 2});
     EXPECT_FALSE(getSnapshot(111)->hasInputInfo());
 
     setBuffer(111);
-
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
     EXPECT_TRUE(getSnapshot(111)->hasInputInfo());
     EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
             gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL));
-    EXPECT_FALSE(getSnapshot(2)->hasInputInfo());
 }
 
 // content dirty test
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index 9bf344c..1335640 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -163,7 +163,7 @@
     setupCommonPreconditions<Case>();
 
     // A hotplug connect event is enqueued for a display
-    Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    Case::Display::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -197,7 +197,7 @@
     setupCommonPreconditions<Case>();
 
     // A hotplug connect event is enqueued for a display
-    Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    Case::Display::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // --------------------------------------------------------------------
     // Invocation
@@ -219,7 +219,7 @@
     setupCommonPreconditions<Case>();
 
     // A hotplug disconnect event is enqueued for a display
-    Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+    Case::Display::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
 
     // The display is already completely set up.
     Case::Display::injectHwcDisplay(this);
@@ -327,9 +327,10 @@
                 setupCommonPreconditions<Case>();
 
                 // A hotplug connect event is enqueued for a display
-                Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+                Case::Display::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
                 // A hotplug disconnect event is also enqueued for the same display
-                Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+                Case::Display::injectPendingHotplugEvent(this,
+                                                         HWComposer::HotplugEvent::Disconnected);
 
                 // --------------------------------------------------------------------
                 // Call Expectations
@@ -378,9 +379,10 @@
                 existing.inject();
 
                 // A hotplug disconnect event is enqueued for a display
-                Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+                Case::Display::injectPendingHotplugEvent(this,
+                                                         HWComposer::HotplugEvent::Disconnected);
                 // A hotplug connect event is also enqueued for the same display
-                Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+                Case::Display::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
                 // --------------------------------------------------------------------
                 // Call Expectations
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 4e7a174..2d986c6 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -41,9 +41,9 @@
     const auto& pendingEvents = mFlinger.mutablePendingHotplugEvents();
     ASSERT_EQ(2u, pendingEvents.size());
     EXPECT_EQ(hwcDisplayId1, pendingEvents[0].hwcDisplayId);
-    EXPECT_EQ(Connection::CONNECTED, pendingEvents[0].connection);
+    EXPECT_EQ(HWComposer::HotplugEvent::Connected, pendingEvents[0].event);
     EXPECT_EQ(hwcDisplayId2, pendingEvents[1].hwcDisplayId);
-    EXPECT_EQ(Connection::DISCONNECTED, pendingEvents[1].connection);
+    EXPECT_EQ(HWComposer::HotplugEvent::Disconnected, pendingEvents[1].event);
 }
 
 TEST_F(HotplugTest, schedulesFrameToCommitDisplayTransaction) {
@@ -64,7 +64,7 @@
     using PrimaryDisplay = InnerDisplayVariant;
     PrimaryDisplay::setupHwcHotplugCallExpectations(this);
     PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
-    PrimaryDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    PrimaryDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // TODO(b/241286146): Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
@@ -80,7 +80,7 @@
     using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
     ExternalDisplay::setupHwcHotplugCallExpectations(this);
     ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // TODO(b/241286146): Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
@@ -123,7 +123,7 @@
     using PrimaryDisplay = PrimaryDisplayVariant;
     PrimaryDisplay::setupHwcHotplugCallExpectations(this);
     PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
-    PrimaryDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    PrimaryDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // TODO(b/241286146): Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
@@ -139,7 +139,7 @@
     using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
     ExternalDisplay::setupHwcHotplugCallExpectations(this);
     ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
     // TODO(b/241286146): Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
@@ -206,15 +206,15 @@
     // A single commit should be scheduled for both configure calls.
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
     mFlinger.configure();
 
     EXPECT_TRUE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
 
     // Disconnecting a display that was already disconnected should be a no-op.
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
     mFlinger.configure();
 
     // The display should be scheduled for removal during the next commit. At this point, it should
@@ -249,14 +249,14 @@
 
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
     mFlinger.configure();
 
     // The hotplug should be rejected, so no HWComposer::DisplayData should be created.
     EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
 
     // Disconnecting a display that does not exist should be a no-op.
-    ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+    ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
     mFlinger.configure();
 
     EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
index 7bd85cd..3fa4093 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
@@ -81,7 +81,7 @@
                 (PhysicalDisplayId, float, float, const Hwc2::Composer::DisplayBrightnessOptions&),
                 (override));
     MOCK_METHOD(std::optional<DisplayIdentificationInfo>, onHotplug,
-                (hal::HWDisplayId, hal::Connection), (override));
+                (hal::HWDisplayId, HWComposer::HotplugEvent), (override));
     MOCK_METHOD(bool, updatesDeviceProductInfoOnHotplugReconnect, (), (const, override));
     MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
     MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, hal::Vsync), (override));