Merge "Use std::unique_ptr for DispatchEntry" into main
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 4081514..a401838 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <binder/BpBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
@@ -117,10 +118,26 @@
     });
 
     if (!found) {
+        std::set<std::string> instances;
+        forEachManifest([&](const ManifestWithDescription& mwd) {
+            std::set<std::string> res = mwd.manifest->getAidlInstances(aname.package, aname.iface);
+            instances.insert(res.begin(), res.end());
+            return true;
+        });
+
+        std::string available;
+        if (instances.empty()) {
+            available = "No alternative instances declared in VINTF";
+        } else {
+            // for logging only. We can't return this information to the client
+            // because they may not have permissions to find or list those
+            // instances
+            available = "VINTF declared instances: " + base::Join(instances, ", ");
+        }
         // Although it is tested, explicitly rebuilding qualified name, in case it
         // becomes something unexpected.
-        ALOGI("Could not find %s.%s/%s in the VINTF manifest.", aname.package.c_str(),
-              aname.iface.c_str(), aname.instance.c_str());
+        ALOGI("Could not find %s.%s/%s in the VINTF manifest. %s.", aname.package.c_str(),
+              aname.iface.c_str(), aname.instance.c_str(), available.c_str());
     }
 
     return found;
diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h
index b857482..14dd463 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -46,7 +46,7 @@
 public:
     InputVerifier(const std::string& name);
 
-    android::base::Result<void> processMovement(int32_t deviceId, int32_t action,
+    android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action,
                                                 uint32_t pointerCount,
                                                 const PointerProperties* pointerProperties,
                                                 const PointerCoords* pointerCoords, int32_t flags);
diff --git a/libs/binder/tests/parcel_fuzzer/random_fd.cpp b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
index e4dbb2d..7390d49 100644
--- a/libs/binder/tests/parcel_fuzzer/random_fd.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
@@ -29,40 +29,65 @@
     const char* fdType;
 
     std::vector<unique_fd> fds = provider->PickValueInArray<
-            std::function<std::vector<unique_fd>()>>({
-            [&]() {
-                fdType = "ashmem";
-                std::vector<unique_fd> ret;
-                ret.push_back(unique_fd(
-                        ashmem_create_region("binder test region",
-                                             provider->ConsumeIntegralInRange<size_t>(0, 4096))));
-                return ret;
-            },
-            [&]() {
-                fdType = "/dev/null";
-                std::vector<unique_fd> ret;
-                ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
-                return ret;
-            },
-            [&]() {
-                fdType = "pipefd";
+            std::function<std::vector<unique_fd>()>>(
+            {[&]() {
+                 fdType = "ashmem";
+                 std::vector<unique_fd> ret;
+                 ret.push_back(unique_fd(
+                         ashmem_create_region("binder test region",
+                                              provider->ConsumeIntegralInRange<size_t>(0, 4096))));
+                 return ret;
+             },
+             [&]() {
+                 fdType = "/dev/null";
+                 std::vector<unique_fd> ret;
+                 ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
+                 return ret;
+             },
+             [&]() {
+                 fdType = "pipefd";
 
-                int pipefds[2];
+                 int pipefds[2];
 
-                int flags = O_CLOEXEC;
-                if (provider->ConsumeBool()) flags |= O_DIRECT;
-                if (provider->ConsumeBool()) flags |= O_NONBLOCK;
+                 int flags = O_CLOEXEC;
+                 if (provider->ConsumeBool()) flags |= O_DIRECT;
+                 if (provider->ConsumeBool()) flags |= O_NONBLOCK;
 
-                CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
+                 CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
 
-                if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
+                 if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
 
-                std::vector<unique_fd> ret;
-                ret.push_back(unique_fd(pipefds[0]));
-                ret.push_back(unique_fd(pipefds[1]));
-                return ret;
-            },
-    })();
+                 std::vector<unique_fd> ret;
+                 ret.push_back(unique_fd(pipefds[0]));
+                 ret.push_back(unique_fd(pipefds[1]));
+                 return ret;
+             },
+             [&]() {
+                 fdType = "tempfd";
+                 char name[PATH_MAX];
+#if defined(__ANDROID__)
+                 snprintf(name, sizeof(name), "/data/local/tmp/android-tempfd-test-%d-XXXXXX",
+                          getpid());
+#else
+                 snprintf(name, sizeof(name), "/tmp/android-tempfd-test-%d-XXXXXX", getpid());
+#endif
+                 int fd = mkstemp(name);
+                 CHECK_NE(fd, -1) << "Failed to create file " << name << ", errno: " << errno;
+                 unlink(name);
+                 if (provider->ConsumeBool()) {
+                     CHECK_NE(TEMP_FAILURE_RETRY(
+                                      ftruncate(fd,
+                                                provider->ConsumeIntegralInRange<size_t>(0, 4096))),
+                              -1)
+                             << "Failed to truncate file, errno: " << errno;
+                 }
+
+                 std::vector<unique_fd> ret;
+                 ret.push_back(unique_fd(fd));
+                 return ret;
+             }
+
+            })();
 
     for (const auto& fd : fds) CHECK(fd.ok()) << fd.get() << " " << fdType;
 
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 4842476..ed5d5c1 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -63,8 +63,7 @@
 namespace {
 static bool isVndkEnabled() {
 #ifdef __BIONIC__
-    // TODO(b/290159430) Use ro.vndk.version to check if VNDK is enabled instead
-    static bool isVndkEnabled = !android::base::GetBoolProperty("ro.vndk.deprecate", false);
+    static bool isVndkEnabled = android::base::GetProperty("ro.vndk.version", "") != "";
     return isVndkEnabled;
 #endif
     return false;
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 9a27d23..f17a654 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -200,6 +200,7 @@
 
 cc_aconfig_library {
     name: "libguiflags",
+    host_supported: true,
     vendor_available: true,
     aconfig_declarations: "libgui_flags",
 }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 36a01d3..ab4af1a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -90,6 +90,28 @@
         "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
         "--allowlist-var=MAX_POINTER_ID",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
+        "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
+        "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
+        "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
+        "--allowlist-var=AINPUT_SOURCE_DPAD",
+        "--allowlist-var=AINPUT_SOURCE_GAMEPAD",
+        "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+        "--allowlist-var=AINPUT_SOURCE_MOUSE",
+        "--allowlist-var=AINPUT_SOURCE_STYLUS",
+        "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
+        "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+        "--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+        "--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+        "--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
+        "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+        "--allowlist-var=AINPUT_SOURCE_HDMI",
+        "--allowlist-var=AINPUT_SOURCE_SENSOR",
+        "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
     ],
 
     static_libs: [
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 1600013..30cedb0 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -632,8 +632,8 @@
                                 MotionEvent::actionToString(action).c_str()));
     if (verifyEvents()) {
         Result<void> result =
-                mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
-                                               pointerCoords, flags);
+                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+                                               pointerProperties, pointerCoords, flags);
         if (!result.ok()) {
             LOG(FATAL) << "Bad stream: " << result.error();
         }
@@ -1276,13 +1276,13 @@
         PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
         const PointerCoords& currentCoords = current->getPointerById(id);
         resampledCoords = currentCoords;
+        resampledCoords.isResampled = true;
         if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
             const PointerCoords& otherCoords = other->getPointerById(id);
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
                                          lerp(currentCoords.getX(), otherCoords.getX(), alpha));
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
                                          lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-            resampledCoords.isResampled = true;
             ALOGD_IF(debugResampling(),
                      "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
                      "other (%0.3f, %0.3f), alpha %0.3f",
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index 6c602e0..cec2445 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -33,7 +33,7 @@
 InputVerifier::InputVerifier(const std::string& name)
       : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
 
-Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t action,
+Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
                                             uint32_t pointerCount,
                                             const PointerProperties* pointerProperties,
                                             const PointerCoords* pointerCoords, int32_t flags) {
@@ -43,8 +43,8 @@
     }
     rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
     rust::String errorMessage =
-            android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
-                                                       static_cast<uint32_t>(flags));
+            android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
+                                                       properties, static_cast<uint32_t>(flags));
     if (errorMessage.empty()) {
         return {};
     } else {
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index a0563f9..e8575a6 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -27,3 +27,10 @@
   description: "Set to true to enable timer support for the touchpad Gestures library"
   bug: "297192727"
 }
+
+flag {
+  name: "enable_multi_device_input"
+  namespace: "input"
+  description: "Set to true to enable multi-device input: touch and stylus can be active at the same time, but in different windows"
+  bug: "211379801"
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 9d3b386..9725b00 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -23,6 +23,54 @@
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub struct DeviceId(pub i32);
 
+#[repr(u32)]
+pub enum SourceClass {
+    None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
+    Button = input_bindgen::AINPUT_SOURCE_CLASS_BUTTON,
+    Pointer = input_bindgen::AINPUT_SOURCE_CLASS_POINTER,
+    Navigation = input_bindgen::AINPUT_SOURCE_CLASS_NAVIGATION,
+    Position = input_bindgen::AINPUT_SOURCE_CLASS_POSITION,
+    Joystick = input_bindgen::AINPUT_SOURCE_CLASS_JOYSTICK,
+}
+
+bitflags! {
+    /// Source of the input device or input events.
+    pub struct Source: u32 {
+        /// SOURCE_UNKNOWN
+        const Unknown = input_bindgen::AINPUT_SOURCE_UNKNOWN;
+        /// SOURCE_KEYBOARD
+        const Keyboard = input_bindgen::AINPUT_SOURCE_KEYBOARD;
+        /// SOURCE_DPAD
+        const Dpad = input_bindgen::AINPUT_SOURCE_DPAD;
+        /// SOURCE_GAMEPAD
+        const Gamepad = input_bindgen::AINPUT_SOURCE_GAMEPAD;
+        /// SOURCE_TOUCHSCREEN
+        const Touchscreen = input_bindgen::AINPUT_SOURCE_TOUCHSCREEN;
+        /// SOURCE_MOUSE
+        const Mouse = input_bindgen::AINPUT_SOURCE_MOUSE;
+        /// SOURCE_STYLUS
+        const Stylus = input_bindgen::AINPUT_SOURCE_STYLUS;
+        /// SOURCE_BLUETOOTH_STYLUS
+        const BluetoothStylus = input_bindgen::AINPUT_SOURCE_BLUETOOTH_STYLUS;
+        /// SOURCE_TRACKBALL
+        const Trackball = input_bindgen::AINPUT_SOURCE_TRACKBALL;
+        /// SOURCE_MOUSE_RELATIVE
+        const MouseRelative = input_bindgen::AINPUT_SOURCE_MOUSE_RELATIVE;
+        /// SOURCE_TOUCHPAD
+        const Touchpad = input_bindgen::AINPUT_SOURCE_TOUCHPAD;
+        /// SOURCE_TOUCH_NAVIGATION
+        const TouchNavigation = input_bindgen::AINPUT_SOURCE_TOUCH_NAVIGATION;
+        /// SOURCE_JOYSTICK
+        const Joystick = input_bindgen::AINPUT_SOURCE_JOYSTICK;
+        /// SOURCE_HDMI
+        const HDMI = input_bindgen::AINPUT_SOURCE_HDMI;
+        /// SOURCE_SENSOR
+        const Sensor = input_bindgen::AINPUT_SOURCE_SENSOR;
+        /// SOURCE_ROTARY_ENCODER
+        const RotaryEncoder = input_bindgen::AINPUT_SOURCE_ROTARY_ENCODER;
+    }
+}
+
 /// A rust enum representation of a MotionEvent action.
 #[repr(u32)]
 pub enum MotionAction {
@@ -134,3 +182,11 @@
         const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
     }
 }
+
+impl Source {
+    /// Return true if this source is from the provided class
+    pub fn is_from_class(&self, source_class: SourceClass) -> bool {
+        let class_bits = source_class as u32;
+        self.bits() & class_bits == class_bits
+    }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 64c0466..2d94e70 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,7 +17,7 @@
 //! Contains the InputVerifier, used to validate a stream of input events.
 
 use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionFlags};
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
 use log::info;
 use std::collections::HashMap;
 use std::collections::HashSet;
@@ -51,10 +51,15 @@
     pub fn process_movement(
         &mut self,
         device_id: DeviceId,
+        source: Source,
         action: u32,
         pointer_properties: &[RustPointerProperties],
         flags: MotionFlags,
     ) -> Result<(), String> {
+        if !source.is_from_class(SourceClass::Pointer) {
+            // Skip non-pointer sources like MOUSE_RELATIVE for now
+            return Ok(());
+        }
         if self.should_log {
             info!(
                 "Processing {} for device {:?} ({} pointer{}) on {}",
@@ -68,6 +73,13 @@
 
         match action.into() {
             MotionAction::Down => {
+                if pointer_properties.len() != 1 {
+                    return Err(format!(
+                        "{}: Invalid DOWN event: there are {} pointers in the event",
+                        self.name,
+                        pointer_properties.len()
+                    ));
+                }
                 let it = self
                     .touching_pointer_ids_by_device
                     .entry(device_id)
@@ -90,10 +102,19 @@
                     ));
                 }
                 let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                if it.len() != pointer_properties.len() - 1 {
+                    return Err(format!(
+                        "{}: There are currently {} touching pointers, but the incoming \
+                         POINTER_DOWN event has {}",
+                        self.name,
+                        it.len(),
+                        pointer_properties.len()
+                    ));
+                }
                 let pointer_id = pointer_properties[action_index].id;
                 if it.contains(&pointer_id) {
                     return Err(format!(
-                        "{}: Pointer with id={} not found in the properties",
+                        "{}: Pointer with id={} already present found in the properties",
                         self.name, pointer_id
                     ));
                 }
@@ -108,11 +129,10 @@
                 }
             }
             MotionAction::PointerUp { action_index } => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
                     return Err(format!(
-                        "{}: Received POINTER_UP but no pointers are currently down for device \
-                        {:?}",
-                        self.name, device_id
+                        "{}: ACTION_POINTER_UP touching pointers don't match",
+                        self.name
                     ));
                 }
                 let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
@@ -120,6 +140,13 @@
                 it.remove(&pointer_id);
             }
             MotionAction::Up => {
+                if pointer_properties.len() != 1 {
+                    return Err(format!(
+                        "{}: Invalid UP event: there are {} pointers in the event",
+                        self.name,
+                        pointer_properties.len()
+                    ));
+                }
                 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
                     return Err(format!(
                         "{} Received ACTION_UP but no pointers are currently down for device {:?}",
@@ -246,6 +273,7 @@
     use crate::DeviceId;
     use crate::MotionFlags;
     use crate::RustPointerProperties;
+    use crate::Source;
     #[test]
     fn single_pointer_stream() {
         let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
@@ -253,6 +281,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -261,6 +290,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -269,6 +299,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -283,6 +314,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -291,6 +323,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -299,6 +332,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(2),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -307,6 +341,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(2),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -315,6 +350,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -329,6 +365,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -337,6 +374,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
                 &pointer_properties,
                 MotionFlags::CANCELED,
@@ -351,6 +389,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -359,6 +398,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
                 &pointer_properties,
                 MotionFlags::empty(), // forgot to set FLAG_CANCELED
@@ -373,6 +413,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_UP,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -387,6 +428,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -396,6 +438,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -405,6 +448,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -414,6 +458,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -428,6 +473,7 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
                 &pointer_properties,
                 MotionFlags::empty(),
@@ -437,10 +483,28 @@
         assert!(verifier
             .process_movement(
                 DeviceId(1),
+                Source::Touchscreen,
                 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
                 &pointer_properties,
                 MotionFlags::empty(),
             )
             .is_err());
     }
+
+    // Send a MOVE without a preceding DOWN event. This is OK because it's from source
+    // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
+    #[test]
+    fn relative_mouse_move() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                Source::MouseRelative,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
 }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 1d3c434..01d9599 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -19,7 +19,7 @@
 mod input;
 mod input_verifier;
 
-pub use input::{DeviceId, MotionAction, MotionFlags};
+pub use input::{DeviceId, MotionAction, MotionFlags, Source};
 pub use input_verifier::InputVerifier;
 
 #[cxx::bridge(namespace = "android::input")]
@@ -51,6 +51,7 @@
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
+            source: u32,
             action: u32,
             pointer_properties: &[RustPointerProperties],
             flags: u32,
@@ -73,12 +74,14 @@
 fn process_movement(
     verifier: &mut InputVerifier,
     device_id: i32,
+    source: u32,
     action: u32,
     pointer_properties: &[RustPointerProperties],
     flags: u32,
 ) -> String {
     let result = verifier.process_movement(
         DeviceId(device_id),
+        Source::from_bits(source).unwrap(),
         action,
         pointer_properties,
         MotionFlags::from_bits(flags).unwrap(),
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 3ecf8ee..06b841b 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -25,7 +25,22 @@
 
 namespace android {
 
-constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION;
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+struct Pointer {
+    int32_t id;
+    float x;
+    float y;
+    bool isResampled = false;
+};
+
+} // namespace
 
 class InputPublisherAndConsumerTest : public testing::Test {
 protected:
@@ -46,12 +61,28 @@
         mConsumer = std::make_unique<InputConsumer>(mClientChannel);
     }
 
-    void PublishAndConsumeKeyEvent();
-    void PublishAndConsumeMotionEvent();
-    void PublishAndConsumeFocusEvent();
-    void PublishAndConsumeCaptureEvent();
-    void PublishAndConsumeDragEvent();
-    void PublishAndConsumeTouchModeEvent();
+    void publishAndConsumeKeyEvent();
+    void publishAndConsumeMotionStream();
+    void publishAndConsumeFocusEvent();
+    void publishAndConsumeCaptureEvent();
+    void publishAndConsumeDragEvent();
+    void publishAndConsumeTouchModeEvent();
+    void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+                                      const std::vector<Pointer>& pointers);
+
+private:
+    // The sequence number to use when publishing the next event
+    uint32_t mSeq = 1;
+
+    void publishAndConsumeMotionEvent(
+            int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+            int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+            int32_t metaState, int32_t buttonState, MotionClassification classification,
+            float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
+            float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
+            float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
+            nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
+            const std::vector<PointerCoords>& pointerCoords);
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -63,10 +94,10 @@
               mConsumer->getChannel()->getConnectionToken());
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() {
     status_t status;
 
-    constexpr uint32_t seq = 15;
+    const uint32_t seq = mSeq++;
     int32_t eventId = InputEvent::nextId();
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
@@ -132,20 +163,43 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
-    status_t status;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    constexpr uint32_t seq = 15;
-    int32_t eventId = InputEvent::nextId();
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
     constexpr std::array<uint8_t, 32> hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
                                               11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                                               22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
-    constexpr int32_t action = AMOTION_EVENT_ACTION_MOVE;
     constexpr int32_t actionButton = 0;
-    constexpr int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    const size_t pointerCount = pointers.size();
     constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
     constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
     constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
@@ -162,20 +216,21 @@
     constexpr float yPrecision = 0.5;
     constexpr float xCursorPosition = 1.3;
     constexpr float yCursorPosition = 50.6;
-    constexpr nsecs_t downTime = 3;
-    constexpr size_t pointerCount = 3;
-    constexpr nsecs_t eventTime = 4;
-    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    PointerProperties pointerProperties[pointerCount];
-    PointerCoords pointerCoords[pointerCount];
+
+    const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
     for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
         pointerProperties[i].clear();
-        pointerProperties[i].id = (i + 2) % pointerCount;
+        pointerProperties[i].id = pointers[i].id;
         pointerProperties[i].toolType = ToolType::FINGER;
 
+        pointerCoords.push_back({});
         pointerCoords[i].clear();
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
-        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
@@ -185,18 +240,40 @@
         pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
     }
 
+    publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
+                                 edgeFlags, metaState, buttonState, classification, xScale, yScale,
+                                 xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
+                                 yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
+                                 downTime, eventTime, pointerProperties, pointerCoords);
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+        int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+        int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+        int32_t buttonState, MotionClassification classification, float xScale, float yScale,
+        float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
+        float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
+        nsecs_t downTime, nsecs_t eventTime,
+        const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerCoords>& pointerCoords) {
+    const uint32_t seq = mSeq++;
+    const int32_t eventId = InputEvent::nextId();
     ui::Transform transform;
     transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
     ui::Transform rawTransform;
     rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+
+    status_t status;
+    ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
+    const size_t pointerCount = pointerProperties.size();
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
                                             xCursorPosition, yCursorPosition, rawTransform,
-                                            downTime, eventTime, pointerCount, pointerProperties,
-                                            pointerCoords);
-    ASSERT_EQ(OK, status)
-            << "publisher publishMotionEvent should return OK";
+                                            downTime, eventTime, pointerCount,
+                                            pointerProperties.data(), pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
 
     uint32_t consumeSeq;
     InputEvent* event;
@@ -280,7 +357,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -321,7 +398,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeCaptureEvent() {
     status_t status;
 
     constexpr uint32_t seq = 42;
@@ -361,7 +438,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeDragEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -405,7 +482,7 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
-void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeTouchModeEvent() {
     status_t status;
 
     constexpr uint32_t seq = 15;
@@ -462,27 +539,27 @@
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishCaptureEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishDragEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
@@ -546,17 +623,29 @@
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
-    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
 } // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 655de80..1cb7f7b 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -27,10 +27,13 @@
 
 namespace android {
 
+namespace {
+
 struct Pointer {
     int32_t id;
     float x;
     float y;
+    ToolType toolType = ToolType::FINGER;
     bool isResampled = false;
 };
 
@@ -40,6 +43,8 @@
     int32_t action;
 };
 
+} // namespace
+
 class TouchResamplingTest : public testing::Test {
 protected:
     std::unique_ptr<InputPublisher> mPublisher;
@@ -99,7 +104,7 @@
         properties.push_back({});
         properties.back().clear();
         properties.back().id = pointer.id;
-        properties.back().toolType = ToolType::FINGER;
+        properties.back().toolType = pointer.toolType;
 
         coords.push_back({});
         coords.back().clear();
@@ -292,6 +297,48 @@
 }
 
 /**
+ * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
+ * a resampled timestamp and should be marked as such.
+ */
+TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+            // A resampled event is generated, but the stylus coordinates are not resampled.
+            {25ms,
+             {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
  * Event should not be resampled when sample time is equal to event time.
  */
 TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
@@ -544,13 +591,13 @@
     // First pointer id=0 leaves the screen
     entries = {
             //      id  x    y
-            {80ms, {{1, 600, 600}}, actionPointer0Up},
+            {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
     };
     publishInputEventEntries(entries);
     frameTime = 90ms;
     expectedEntries = {
             //      id  x    y
-            {80ms, {{1, 600, 600}}, actionPointer0Up},
+            {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
             // no resampled event for ACTION_POINTER_UP
     };
     consumeInputEventEntries(expectedEntries, frameTime);
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 8d8a2bc..342f5de 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -73,6 +73,8 @@
         "libGLESv2",
     ],
 
+    static_libs: ["libguiflags"],
+
     export_header_lib_headers: ["jni_headers"],
 
     header_libs: [
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 0f119f3..32fb350 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,6 +19,7 @@
 #include <android/hardware_buffer.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/ConsumerBase.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <sys/cdefs.h>
 #include <system/graphics.h>
@@ -290,6 +291,20 @@
      */
     void releaseConsumerOwnership();
 
+    /**
+     * Interface for SurfaceTexture callback(s).
+     */
+    struct SurfaceTextureListener : public RefBase {
+        virtual void onFrameAvailable(const BufferItem& item) = 0;
+        virtual void onSetFrameRate(float frameRate, int8_t compatibility,
+                                    int8_t changeFrameRateStrategy) = 0;
+    };
+
+    /**
+     * setSurfaceTextureListener registers a SurfaceTextureListener.
+     */
+    void setSurfaceTextureListener(const sp<SurfaceTextureListener>&);
+
 protected:
     /**
      * abandonLocked overrides the ConsumerBase method to clear
@@ -335,6 +350,14 @@
     void computeCurrentTransformMatrixLocked();
 
     /**
+     * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
+     */
+#if FLAG_BQ_SET_FRAME_RATE
+    void onSetFrameRate(float frameRate, int8_t compatibility,
+                        int8_t changeFrameRateStrategy) override;
+#endif
+
+    /**
      * The default consumer usage flags that SurfaceTexture always sets on its
      * BufferQueue instance; these will be OR:d with any additional flags passed
      * from the SurfaceTexture user. In particular, SurfaceTexture will always
@@ -465,8 +488,30 @@
      */
     ImageConsumer mImageConsumer;
 
+    /**
+     * mSurfaceTextureListener holds the registered SurfaceTextureListener.
+     * Note that SurfaceTexture holds the lister with an sp<>, which means that the listener
+     * must only hold a wp<> to SurfaceTexture and not an sp<>.
+     */
+    sp<SurfaceTextureListener> mSurfaceTextureListener;
+
     friend class ImageConsumer;
     friend class EGLConsumer;
+
+private:
+    // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
+    // is extending ConsumerBase which also implements FrameAvailableListener.
+    class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
+    public:
+        FrameAvailableListenerProxy(const wp<SurfaceTextureListener>& listener)
+              : mSurfaceTextureListener(listener) {}
+
+    private:
+        void onFrameAvailable(const BufferItem& item) override;
+
+        const wp<SurfaceTextureListener> mSurfaceTextureListener;
+    };
+    sp<FrameAvailableListenerProxy> mFrameAvailableListenerProxy;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 9f610e1..c2535e0 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -23,6 +23,8 @@
 #include <system/window.h>
 #include <utils/Trace.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
 // Macros for including the SurfaceTexture name in log messages
@@ -491,4 +493,42 @@
     return buffer;
 }
 
+void SurfaceTexture::setSurfaceTextureListener(
+        const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
+    SFT_LOGV("setSurfaceTextureListener");
+
+    Mutex::Autolock _l(mMutex);
+    mSurfaceTextureListener = listener;
+    if (mSurfaceTextureListener != nullptr) {
+        mFrameAvailableListenerProxy =
+                sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
+        setFrameAvailableListener(mFrameAvailableListenerProxy);
+    } else {
+        mFrameAvailableListenerProxy.clear();
+    }
+}
+
+void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
+    const auto listener = mSurfaceTextureListener.promote();
+    if (listener) {
+        listener->onFrameAvailable(item);
+    }
+}
+
+#if FLAG_BQ_SET_FRAME_RATE
+void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
+                                    int8_t changeFrameRateStrategy) {
+    SFT_LOGV("onSetFrameRate: %.2f", frameRate);
+
+    auto listener = [&] {
+        Mutex::Autolock _l(mMutex);
+        return mSurfaceTextureListener;
+    }();
+
+    if (listener) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 } // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index cc1d12b..2053c6a 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -27,6 +27,7 @@
 #include <GrTypes.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <gui/TraceUtils.h>
 #include <sync/sync.h>
 #include <ui/DebugUtils.h>
@@ -299,10 +300,10 @@
     LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContext::MakeGL(glInterface, options);
+    contexts.first = GrDirectContexts::MakeGL(glInterface, options);
     if (supportsProtectedContentImpl()) {
         useProtectedContextImpl(GrProtected::kYes);
-        contexts.second = GrDirectContext::MakeGL(glInterface, options);
+        contexts.second = GrDirectContexts::MakeGL(glInterface, options);
         useProtectedContextImpl(GrProtected::kNo);
     }
 
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 7ffdac7..ed3c616 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -48,9 +48,8 @@
 void freeHotCacheEntry(android::MultifileHotCache& entry) {
     if (entry.entryFd != -1) {
         // If we have an fd, then this entry was added to hot cache via INIT or GET
-        // We need to unmap and close the entry
+        // We need to unmap the entry
         munmap(entry.entryBuffer, entry.entrySize);
-        close(entry.entryFd);
     } else {
         // Otherwise, this was added to hot cache during SET, so it was never mapped
         // and fd was only on the deferred thread.
@@ -143,6 +142,7 @@
                 if (result != sizeof(MultifileHeader)) {
                     ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
                           fullPath.c_str(), std::strerror(errno));
+                    close(fd);
                     return;
                 }
 
@@ -152,6 +152,7 @@
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
+                    close(fd);
                     continue;
                 }
 
@@ -161,6 +162,10 @@
                 // Memory map the file
                 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
                         mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+                // We can close the file now and the mmap will remain
+                close(fd);
+
                 if (mappedEntry == MAP_FAILED) {
                     ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
                     return;
@@ -206,13 +211,11 @@
                     if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
                         ALOGE("INIT Failed to add %u to hot cache", entryHash);
                         munmap(mappedEntry, fileSize);
-                        close(fd);
                         return;
                     }
                 } else {
                     // If we're not keeping it in hot cache, unmap it now
                     munmap(mappedEntry, fileSize);
-                    close(fd);
                 }
             }
             closedir(dir);
@@ -401,9 +404,12 @@
         // Memory map the file
         cacheEntry =
                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+        // We can close the file now and the mmap will remain
+        close(fd);
+
         if (cacheEntry == MAP_FAILED) {
             ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
-            close(fd);
             return 0;
         }
 
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index dbee13b..1639be6 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -42,6 +42,8 @@
 
     virtual void TearDown() { mMBC.reset(); }
 
+    int getFileDescriptorCount();
+
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
 };
@@ -216,4 +218,56 @@
     ASSERT_EQ('y', buf[0]);
 }
 
+int MultifileBlobCacheTest::getFileDescriptorCount() {
+    DIR* directory = opendir("/proc/self/fd");
+
+    int fileCount = 0;
+    struct dirent* entry;
+    while ((entry = readdir(directory)) != NULL) {
+        fileCount++;
+        // printf("File: %s\n", entry->d_name);
+    }
+
+    closedir(directory);
+    return fileCount;
+}
+
+TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
+    // Populate the cache with a bunch of entries
+    size_t kLargeNumberOfEntries = 1024;
+    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+        // printf("Caching: %i", i);
+
+        // Use the index as the key and value
+        mMBC->set(&i, sizeof(i), &i, sizeof(i));
+
+        int result = 0;
+        ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+        ASSERT_EQ(i, result);
+    }
+
+    // Ensure we don't have a bunch of open fds
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Now open it again and ensure we still don't have a bunch of open fds
+    mMBC.reset(
+            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+
+    // Check after initialization
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+        int result = 0;
+        ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+        ASSERT_EQ(i, result);
+    }
+
+    // And again after we've actually used it
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+}
+
 } // namespace android
diff --git a/services/gpuservice/gpumem/GpuMem.cpp b/services/gpuservice/gpumem/GpuMem.cpp
index dd3cc3b..141fe02 100644
--- a/services/gpuservice/gpumem/GpuMem.cpp
+++ b/services/gpuservice/gpumem/GpuMem.cpp
@@ -77,7 +77,7 @@
     mInitialized.store(true);
 }
 
-void GpuMem::setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+void GpuMem::setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
     mGpuMemTotalMap = std::move(map);
 }
 
diff --git a/services/gpuservice/gpumem/include/gpumem/GpuMem.h b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
index 7588b54..9aa74d6 100644
--- a/services/gpuservice/gpumem/include/gpumem/GpuMem.h
+++ b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
@@ -44,12 +44,12 @@
     friend class TestableGpuMem;
 
     // set gpu memory total map
-    void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map);
+    void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map);
 
     // indicate whether ebpf has been initialized
     std::atomic<bool> mInitialized = false;
     // bpf map for GPU memory total data
-    android::bpf::BpfMap<uint64_t, uint64_t> mGpuMemTotalMap;
+    android::bpf::BpfMapRO<uint64_t, uint64_t> mGpuMemTotalMap;
 
     // gpu memory tracepoint event category
     static constexpr char kGpuMemTraceGroup[] = "gpu_mem";
diff --git a/services/gpuservice/tests/unittests/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
index 8dabe4f..1f5b288 100644
--- a/services/gpuservice/tests/unittests/GpuMemTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -66,9 +66,7 @@
         mTestableGpuMem = TestableGpuMem(mGpuMem.get());
         mTestableGpuMem.setInitialized();
         errno = 0;
-        mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
-                                                             TEST_MAP_SIZE,
-                                                             BPF_F_NO_PREALLOC));
+        mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
 
         EXPECT_EQ(0, errno);
         EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
index 5c04210..6550df9 100644
--- a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
@@ -65,9 +65,7 @@
         mTestableGpuMem = TestableGpuMem(mGpuMem.get());
 
         errno = 0;
-        mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
-                                                             TEST_MAP_SIZE,
-                                                             BPF_F_NO_PREALLOC));
+        mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
 
         EXPECT_EQ(0, errno);
         EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/TestableGpuMem.h b/services/gpuservice/tests/unittests/TestableGpuMem.h
index 6c8becb..f21843f 100644
--- a/services/gpuservice/tests/unittests/TestableGpuMem.h
+++ b/services/gpuservice/tests/unittests/TestableGpuMem.h
@@ -28,7 +28,7 @@
 
     void setInitialized() { mGpuMem->mInitialized.store(true); }
 
-    void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+    void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
         mGpuMem->setGpuMemTotalMap(map);
     }
 
diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h
index 5693848..f848c82 100644
--- a/services/inputflinger/BlockingQueue.h
+++ b/services/inputflinger/BlockingQueue.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <condition_variable>
+#include <functional>
 #include <list>
 #include <mutex>
 #include <optional>
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 56a3fb4..8e04150 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -64,14 +64,13 @@
 class : public InputDeviceMetricsLogger {
     nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         const int32_t durationMillis =
                 std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
         const static std::vector<int32_t> empty;
-        const auto& identifier = info.getIdentifier();
 
-        ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
+        ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
         ALOGD_IF(DEBUG, "    Total duration: %dms", durationMillis);
         ALOGD_IF(DEBUG, "    Source breakdown:");
 
@@ -96,11 +95,9 @@
             ALOGD_IF(DEBUG, "        - uid: %s\t duration: %dms", uid.toString().c_str(),
                      durMillis);
         }
-        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
-                          identifier.version,
-                          linuxBusToInputDeviceBusEnum(identifier.bus,
-                                                       info.getUsiVersion().has_value()),
-                          durationMillis, sources, durationsPerSource, uids, durationsPerUid);
+        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
+                          linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
+                          sources, durationsPerSource, uids, durationsPerUid);
     }
 } sStatsdLogger;
 
@@ -116,7 +113,7 @@
 
 } // namespace
 
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo& info,
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
                                                 const NotifyKeyArgs& keyArgs) {
     if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
         return InputDeviceUsageSource::UNKNOWN;
@@ -132,7 +129,7 @@
         return InputDeviceUsageSource::GAMEPAD;
     }
 
-    if (info.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+    if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
         return InputDeviceUsageSource::KEYBOARD;
     }
 
@@ -232,8 +229,8 @@
 
 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
     reportCompletedSessions();
-    const SourceProvider getSources = [&args](const InputDeviceInfo& info) {
-        return std::set{getUsageSourceForKeyArgs(info, args)};
+    const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+        return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
     };
     onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
 
@@ -291,13 +288,23 @@
 }
 
 void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
-    std::map<DeviceId, InputDeviceInfo> newDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
 
     for (const InputDeviceInfo& info : infos) {
         if (isIgnoredInputDeviceId(info.getId())) {
             continue;
         }
-        newDeviceInfos.emplace(info.getId(), info);
+        const auto& i = info.getIdentifier();
+        newDeviceInfos.emplace(info.getId(),
+                               MetricsDeviceInfo{
+                                       .deviceId = info.getId(),
+                                       .vendor = i.vendor,
+                                       .product = i.product,
+                                       .version = i.version,
+                                       .bus = i.bus,
+                                       .isUsiStylus = info.getUsiVersion().has_value(),
+                                       .keyboardType = info.getKeyboardType(),
+                               });
     }
 
     for (auto [deviceId, info] : mLoggedDeviceInfos) {
@@ -311,7 +318,7 @@
 }
 
 void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
-                                                       const InputDeviceInfo& info) {
+                                                       const MetricsDeviceInfo& info) {
     auto it = mActiveUsageSessions.find(deviceId);
     if (it == mActiveUsageSessions.end()) {
         return;
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 1f7c5d9..7775087 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -79,7 +79,7 @@
 };
 
 /** Returns the InputDeviceUsageSource that corresponds to the key event. */
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo&, const NotifyKeyArgs&);
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
 
 /** Returns the InputDeviceUsageSources that correspond to the motion event. */
 std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
@@ -110,7 +110,19 @@
         UidUsageBreakdown uidBreakdown;
     };
 
-    virtual void logInputDeviceUsageReported(const InputDeviceInfo&, const DeviceUsageReport&) = 0;
+    // A subset of information from the InputDeviceInfo class that is used for metrics collection,
+    // used to avoid copying and storing all of the fields and strings in InputDeviceInfo.
+    struct MetricsDeviceInfo {
+        int32_t deviceId;
+        int32_t vendor;
+        int32_t product;
+        int32_t version;
+        int32_t bus;
+        bool isUsiStylus;
+        int32_t keyboardType;
+    };
+    virtual void logInputDeviceUsageReported(const MetricsDeviceInfo&,
+                                             const DeviceUsageReport&) = 0;
     virtual ~InputDeviceMetricsLogger() = default;
 };
 
@@ -153,8 +165,9 @@
     }
 
     using Uid = gui::Uid;
+    using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
 
-    std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos;
 
     using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
     SyncQueue<Interaction> mInteractionsQueue;
@@ -188,8 +201,9 @@
     std::map<DeviceId, ActiveSession> mActiveUsageSessions;
 
     void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
-    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceInfo& info);
-    using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
+    void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info);
+    using SourceProvider =
+            std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
     void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
                             const SourceProvider& getSources);
     void onInputDeviceInteraction(const Interaction&);
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index f889de5..0f62324 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -18,6 +18,7 @@
 #include "UnwantedInteractionBlocker.h"
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <inttypes.h>
@@ -28,6 +29,8 @@
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
 #include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
 
+namespace input_flags = com::android::input::flags;
+
 using android::base::StringPrintf;
 
 /**
@@ -344,10 +347,14 @@
     ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
-        const std::vector<NotifyMotionArgs> processedArgs =
-                mPreferStylusOverTouchBlocker.processMotion(args);
-        for (const NotifyMotionArgs& loopArgs : processedArgs) {
-            notifyMotionLocked(loopArgs);
+        if (input_flags::enable_multi_device_input()) {
+            notifyMotionLocked(args);
+        } else {
+            const std::vector<NotifyMotionArgs> processedArgs =
+                    mPreferStylusOverTouchBlocker.processMotion(args);
+            for (const NotifyMotionArgs& loopArgs : processedArgs) {
+                notifyMotionLocked(loopArgs);
+            }
         }
     } // release lock
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f037b87..4251682 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1294,7 +1294,7 @@
 }
 
 std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
-        int32_t displayId, const sp<WindowInfoHandle>& touchedWindow) const {
+        int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
     if (touchedWindow == nullptr) {
         return {};
     }
@@ -1310,9 +1310,10 @@
 
         const WindowInfo& info = *windowHandle->getInfo();
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
-            addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
-                                  /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
-                                  outsideTargets);
+            std::bitset<MAX_POINTER_ID + 1> pointerIds;
+            pointerIds.set(pointerId);
+            addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, pointerIds,
+                                  /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
         }
     }
     return outsideTargets;
@@ -2334,6 +2335,7 @@
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
         const auto [x, y] = resolveTouchedPosition(entry);
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
         // Outside targets should be added upon first dispatched DOWN event. That means, this should
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
@@ -2341,7 +2343,7 @@
                 findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle);
+            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
@@ -2399,7 +2401,6 @@
 
             if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
                 maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                const int32_t pointerId = entry.pointerProperties[0].id;
                 // The "windowHandle" is the target of this hovering pointer.
                 tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
             }
@@ -2424,7 +2425,7 @@
             // Update the temporary touch state.
             std::bitset<MAX_POINTER_ID + 1> pointerIds;
             if (!isHoverAction) {
-                pointerIds.set(entry.pointerProperties[pointerIndex].id);
+                pointerIds.set(pointerId);
             }
 
             const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
@@ -2468,7 +2469,6 @@
         // If a window is already pilfering some pointers, give it this new pointer as well and
         // make it pilfering. This will prevent other non-spy windows from getting this pointer,
         // which is a specific behaviour that we want.
-        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
         for (TouchedWindow& touchedWindow : tempTouchState.windows) {
             if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
                 touchedWindow.hasPilferingPointers(entry.deviceId)) {
@@ -4321,9 +4321,9 @@
                 mVerifiersByDisplay.try_emplace(args.displayId,
                                                 StringPrintf("display %" PRId32, args.displayId));
         Result<void> result =
-                it->second.processMovement(args.deviceId, args.action, args.getPointerCount(),
-                                           args.pointerProperties.data(), args.pointerCoords.data(),
-                                           args.flags);
+                it->second.processMovement(args.deviceId, args.source, args.action,
+                                           args.getPointerCount(), args.pointerProperties.data(),
+                                           args.pointerCoords.data(), args.flags);
         if (!result.ok()) {
             LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
         }
@@ -4528,7 +4528,7 @@
     // the injected event, it is responsible for setting POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY.
     // For those events, we will set FLAG_IS_ACCESSIBILITY_EVENT to allow apps to distinguish them
     // from events that originate from actual hardware.
-    int32_t resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
+    DeviceId resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
     if (policyFlags & POLICY_FLAG_FILTERED) {
         resolvedDeviceId = event->getDeviceId();
     }
@@ -5915,14 +5915,16 @@
 status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
     const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
     if (!requestingChannel) {
-        ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+        LOG(WARNING)
+                << "Attempted to pilfer pointers from an un-registered channel or invalid token";
         return BAD_VALUE;
     }
 
     auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
     if (statePtr == nullptr || windowPtr == nullptr) {
-        ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
-              " Ignoring.");
+        LOG(WARNING)
+                << "Attempted to pilfer points from a channel without any on-going pointer streams."
+                   " Ignoring.";
         return BAD_VALUE;
     }
     std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
@@ -5931,36 +5933,38 @@
                      << " in window: " << windowPtr->dump();
         return BAD_VALUE;
     }
-    const int32_t deviceId = *deviceIds.begin();
 
-    TouchState& state = *statePtr;
-    TouchedWindow& window = *windowPtr;
-    // Send cancel events to all the input channels we're stealing from.
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                               "input channel stole pointer stream");
-    options.deviceId = deviceId;
-    options.displayId = displayId;
-    std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
-    options.pointerIds = pointerIds;
-    std::string canceledWindows;
-    for (const TouchedWindow& w : state.windows) {
-        const std::shared_ptr<InputChannel> channel =
-                getInputChannelLocked(w.windowHandle->getToken());
-        if (channel != nullptr && channel->getConnectionToken() != token) {
-            synthesizeCancelationEventsForInputChannelLocked(channel, options);
-            canceledWindows += canceledWindows.empty() ? "[" : ", ";
-            canceledWindows += channel->getName();
+    for (const DeviceId deviceId : deviceIds) {
+        TouchState& state = *statePtr;
+        TouchedWindow& window = *windowPtr;
+        // Send cancel events to all the input channels we're stealing from.
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                   "input channel stole pointer stream");
+        options.deviceId = deviceId;
+        options.displayId = displayId;
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+        options.pointerIds = pointerIds;
+        std::string canceledWindows;
+        for (const TouchedWindow& w : state.windows) {
+            const std::shared_ptr<InputChannel> channel =
+                    getInputChannelLocked(w.windowHandle->getToken());
+            if (channel != nullptr && channel->getConnectionToken() != token) {
+                synthesizeCancelationEventsForInputChannelLocked(channel, options);
+                canceledWindows += canceledWindows.empty() ? "[" : ", ";
+                canceledWindows += channel->getName();
+            }
         }
+        canceledWindows += canceledWindows.empty() ? "[]" : "]";
+        LOG(INFO) << "Channel " << requestingChannel->getName()
+                  << " is stealing input gesture for device " << deviceId << " from "
+                  << canceledWindows;
+
+        // Prevent the gesture from being sent to any other windows.
+        // This only blocks relevant pointers to be sent to other windows
+        window.addPilferingPointers(deviceId, pointerIds);
+
+        state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
     }
-    canceledWindows += canceledWindows.empty() ? "[]" : "]";
-    ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
-          canceledWindows.c_str());
-
-    // Prevent the gesture from being sent to any other windows.
-    // This only blocks relevant pointers to be sent to other windows
-    window.addPilferingPointers(deviceId, pointerIds);
-
-    state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
     return OK;
 }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 00c2acb..62e2d58 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -242,8 +242,8 @@
             int32_t displayId, float x, float y, bool isStylus = false,
             bool ignoreDragWindow = false) const REQUIRES(mLock);
     std::vector<InputTarget> findOutsideTargetsLocked(
-            int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow) const
-            REQUIRES(mLock);
+            int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+            int32_t pointerId) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index ccffe26..b21427d 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -83,6 +83,11 @@
     }
 }
 
+/**
+ * Return:
+ *  true if the incoming event was correctly tracked,
+ *  false if the incoming event should be dropped.
+ */
 bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
     int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
     switch (actionMasked) {
@@ -310,7 +315,7 @@
         nsecs_t currentTime) {
     std::vector<std::unique_ptr<EventEntry>> events;
     for (MotionMemento& memento : mMotionMementos) {
-        if (!(memento.source & AINPUT_SOURCE_CLASS_POINTER)) {
+        if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
             continue;
         }
 
@@ -443,7 +448,7 @@
         MotionMemento& memento = mMotionMementos[i];
         // Since we support split pointers we need to merge touch events
         // from the same source + device + screen.
-        if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+        if (isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
             bool merged = false;
             for (size_t j = 0; j < other.mMotionMementos.size(); j++) {
                 MotionMemento& otherMemento = other.mMotionMementos[j];
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 11f3413..343630c 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -16,7 +16,9 @@
 
 #include "InputTarget.h"
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
 #include <inttypes.h>
 #include <string>
 
@@ -34,7 +36,10 @@
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
-    LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any());
+    if ((pointerIds & newPointerIds).any()) {
+        LOG(FATAL) << __func__ << " - overlap with incoming pointers "
+                   << bitsetToString(newPointerIds) << " in " << *this;
+    }
 
     pointerIds |= newPointerIds;
     for (size_t i = 0; i < newPointerIds.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 58b29b8..531fc67 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -451,7 +451,8 @@
 void KeyboardInputMapper::onKeyDownProcessed() {
     InputReaderContext& context = *getContext();
     if (context.isPreventingTouchpadTaps()) {
-        // avoid pinging java service unnecessarily
+        // avoid pinging java service unnecessarily, just fade pointer again if it became visible
+        context.fadePointer();
         return;
     }
     // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 6774b17..e630915 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -28,6 +28,7 @@
 
 namespace android {
 
+using testing::AllOf;
 using testing::Return;
 using testing::VariantWith;
 constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
@@ -36,6 +37,7 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
 
 /**
  * Unit tests for CursorInputMapper.
@@ -58,10 +60,23 @@
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
 
-        EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1));
-
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
+
+    void setPointerCapture(bool enabled) {
+        mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+        mReaderConfiguration.pointerCaptureRequest.seq = 1;
+        int32_t generation = mDevice->getGeneration();
+        std::list<NotifyArgs> args =
+                mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                     InputReaderConfiguration::Change::POINTER_CAPTURE);
+        ASSERT_THAT(args,
+                    ElementsAre(
+                            VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+
+        // Check that generation also got bumped
+        ASSERT_GT(mDevice->getGeneration(), generation);
+    }
 };
 
 /**
@@ -102,4 +117,83 @@
                             VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
 }
 
+/**
+ * Set pointer capture and check that ACTION_MOVE events are emitted from CursorInputMapper.
+ * During pointer capture, source should be set to MOUSE_RELATIVE. When the capture is disabled,
+ * the events should be generated normally:
+ *   1) The source should return to SOURCE_MOUSE
+ *   2) Cursor position should be incremented by the relative device movements
+ *   3) Cursor position of NotifyMotionArgs should now be getting populated.
+ * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
+ */
+TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+    setPointerCapture(true);
+    std::list<NotifyArgs> args;
+
+    // Move.
+    args += process(EV_REL, REL_X, 10);
+    args += process(EV_REL, REL_Y, 20);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(ACTION_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+                              WithCursorPosition(INVALID_CURSOR_POSITION,
+                                                 INVALID_CURSOR_POSITION)))));
+
+    // Button press.
+    args.clear();
+    args += process(EV_KEY, BTN_MOUSE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(ACTION_DOWN),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+
+    // Button release.
+    args.clear();
+    args += process(EV_KEY, BTN_MOUSE, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(BUTTON_RELEASE),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(ACTION_UP),
+                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+
+    // Another move.
+    args.clear();
+    args += process(EV_REL, REL_X, 30);
+    args += process(EV_REL, REL_Y, 40);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(ACTION_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithCoords(30.0f, 40.0f)))));
+
+    // Disable pointer capture. Afterwards, events should be generated the usual way.
+    setPointerCapture(false);
+
+    args.clear();
+    args += process(EV_REL, REL_X, 10);
+    args += process(EV_REL, REL_Y, 20);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                              WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f),
+                              WithCursorPosition(INITIAL_CURSOR_X + 10.0f,
+                                                 INITIAL_CURSOR_Y + 20.0f)))));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 7ccfaca..fdf9ed1 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -66,21 +66,14 @@
 }
 
 InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID,
-                                       uint32_t sources = TOUCHSCREEN | STYLUS,
-                                       bool isAlphabetic = false) {
+                                       uint32_t sources = TOUCHSCREEN | STYLUS) {
     auto info = InputDeviceInfo();
     info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
                     "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
     info.addSource(sources);
-    info.setKeyboardType(isAlphabetic ? AINPUT_KEYBOARD_TYPE_ALPHABETIC
-                                      : AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
     return info;
 }
 
-const InputDeviceInfo ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/true);
-const InputDeviceInfo NON_ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/false);
 const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
 const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
 
@@ -106,7 +99,7 @@
     switch (usageSource) {
         case InputDeviceUsageSource::UNKNOWN: {
             ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
-                      getUsageSourceForKeyArgs(generateTestDeviceInfo(),
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
                                                        .build()));
 
@@ -123,7 +116,7 @@
 
         case InputDeviceUsageSource::BUTTONS: {
             ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
                                                        .build()));
@@ -132,7 +125,7 @@
 
         case InputDeviceUsageSource::KEYBOARD: {
             ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .build()));
             break;
@@ -140,13 +133,13 @@
 
         case InputDeviceUsageSource::DPAD: {
             ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_DPAD_CENTER)
                                                        .build()));
 
             ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_DPAD_CENTER)
                                                        .build()));
@@ -155,13 +148,13 @@
 
         case InputDeviceUsageSource::GAMEPAD: {
             ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_BUTTON_A)
                                                        .build()));
 
             ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_BUTTON_A)
                                                        .build()));
@@ -358,7 +351,13 @@
                            std::optional<UidUsageBreakdown> uidBreakdown = {}) {
         ASSERT_GE(mLoggedUsageSessions.size(), 1u);
         const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
-        ASSERT_EQ(info.getIdentifier(), loggedInfo.getIdentifier());
+        const auto& i = info.getIdentifier();
+        ASSERT_EQ(info.getId(), loggedInfo.deviceId);
+        ASSERT_EQ(i.vendor, loggedInfo.vendor);
+        ASSERT_EQ(i.product, loggedInfo.product);
+        ASSERT_EQ(i.version, loggedInfo.version);
+        ASSERT_EQ(i.bus, loggedInfo.bus);
+        ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus);
         ASSERT_EQ(duration, report.usageDuration);
         if (sourceBreakdown) {
             ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
@@ -389,12 +388,12 @@
     }
 
 private:
-    std::vector<std::tuple<InputDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
+    std::vector<std::tuple<MetricsDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
     nanoseconds mCurrentTime{TIME};
 
     nanoseconds getCurrentTime() override { return mCurrentTime; }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         mLoggedUsageSessions.emplace_back(info, report);
     }
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 38970f2..f0602df 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -28,6 +28,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
+#include <input/PrintTools.h>
 #include <linux/input.h>
 #include <sys/epoll.h>
 
@@ -50,6 +51,8 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 
+namespace {
+
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
 
@@ -136,6 +139,10 @@
     auto operator<=>(const PointF&) const = default;
 };
 
+inline std::string pointFToString(const PointF& p) {
+    return std::string("(") + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
+}
+
 /**
  * Return a DOWN key event with KEYCODE_A.
  */
@@ -148,13 +155,8 @@
     return event;
 }
 
-static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
-    ASSERT_EQ(expectedAction, receivedAction)
-            << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
-            << MotionEvent::actionToString(receivedAction);
-}
-
 MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+    *result_listener << "expected downTime " << downTime << ", but got " << arg.getDownTime();
     return arg.getDownTime() == downTime;
 }
 
@@ -165,6 +167,7 @@
 }
 
 MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
+    *result_listener << "expected flags " << std::hex << flags << ", but got " << arg.getFlags();
     return arg.getFlags() == flags;
 }
 
@@ -173,10 +176,16 @@
         *result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
         return false;
     }
-    return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
+    const float receivedX = arg.getX(/*pointerIndex=*/0);
+    const float receivedY = arg.getY(/*pointerIndex=*/0);
+    *result_listener << "expected coords (" << x << ", " << y << "), but got (" << receivedX << ", "
+                     << receivedY << ")";
+    return receivedX == x && receivedY == y;
 }
 
 MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") {
+    *result_listener << "expected pointerCount " << pointerCount << ", but got "
+                     << arg.getPointerCount();
     return arg.getPointerCount() == pointerCount;
 }
 
@@ -187,6 +196,8 @@
         const int32_t pointerId = arg.getPointerId(pointerIndex);
         actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)};
     }
+    *result_listener << "expected pointers " << dumpMap(pointers, constToString, pointFToString)
+                     << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
     return pointers == actualPointers;
 }
 
@@ -617,6 +628,7 @@
         mFilteredEvent = nullptr;
     }
 };
+} // namespace
 
 // --- InputDispatcherTest ---
 
@@ -959,7 +971,7 @@
         switch (expectedEventType) {
             case InputEventType::KEY: {
                 const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
-                EXPECT_EQ(expectedAction, keyEvent.getAction());
+                ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
                 }
@@ -967,8 +979,7 @@
             }
             case InputEventType::MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                assertMotionAction(expectedAction, motionEvent.getAction());
-
+                ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
                 }
@@ -6467,7 +6478,7 @@
         ASSERT_NE(nullptr, motionEvent)
                 << name.c_str() << ": consumer should have returned non-NULL event.";
 
-        assertMotionAction(expectedAction, motionEvent->getAction());
+        ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
         ASSERT_EQ(points.size(), motionEvent->getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
@@ -6848,6 +6859,10 @@
 // We have a focused application, but no focused window
 // Make sure that we don't notify policy twice about the same ANR.
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
+    const std::chrono::duration appTimeout = 400ms;
+    mApplication->setDispatchingTimeout(appTimeout);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
     mWindow->setFocusable(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
     mWindow->consumeFocusEvent(false);
@@ -6855,13 +6870,18 @@
     // Once a focused event arrives, we get an ANR for this application
     // We specify the injection timeout to be smaller than the application timeout, to ensure that
     // injection times out (instead of failing).
+    const std::chrono::duration eventInjectionTimeout = 100ms;
+    ASSERT_LT(eventInjectionTimeout, appTimeout);
     const InputEventInjectionResult result =
             injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 100ms, /*allowKeyRepeat=*/false);
-    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
-    const std::chrono::duration appTimeout =
-            mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(appTimeout, mApplication);
+                      InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+                      /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
+            << "result=" << ftl::enum_string(result);
+    // We already waited for 'eventInjectionTimeout`, because the countdown started when the event
+    // was first injected. So now we have (appTimeout - eventInjectionTimeout) left to wait.
+    std::chrono::duration remainingWaitTime = appTimeout - eventInjectionTimeout;
+    mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(remainingWaitTime, mApplication);
 
     std::this_thread::sleep_for(appTimeout);
     // ANR should not be raised again. It is up to policy to do that if it desires.
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 0eee2b9..dac4ea0 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -27,7 +27,7 @@
 void InputMapperUnitTest::SetUp() {
     mFakePointerController = std::make_shared<FakePointerController>();
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(400, 240);
+    mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
 
     EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
             .WillRepeatedly(Return(mFakePointerController));
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 909bd9c..c2ac258 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -39,6 +39,8 @@
 protected:
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr float INITIAL_CURSOR_X = 400;
+    static constexpr float INITIAL_CURSOR_Y = 240;
     virtual void SetUp() override;
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 6539593..6032e30 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -4744,92 +4744,6 @@
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
-TEST_F(CursorInputMapperTest, Process_PointerCapture) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    mFakePolicy->setPointerCapture(true);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    // Move.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
-    // Button press.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
-    // Button release.
-    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
-    // Another move.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
-    // Disable pointer capture and check that the device generation got bumped
-    // and events are generated the usual way.
-    const uint32_t generation = mReader->getContext()->getGeneration();
-    mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
 /**
  * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
  * pointer acceleration or speed processing should not be applied.
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 5d50b94..05823cd 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -59,7 +59,7 @@
                 (int32_t deviceId), (override));
 
     MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
-    MOCK_METHOD(int32_t, bumpGeneration, (), (override));
+    int32_t bumpGeneration() override { return ++mGeneration; }
 
     MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
                 (override));
@@ -76,6 +76,9 @@
 
     MOCK_METHOD(void, setPreventingTouchpadTaps, (bool prevent), (override));
     MOCK_METHOD(bool, isPreventingTouchpadTaps, (), (override));
+
+private:
+    int32_t mGeneration = 0;
 };
 
 class MockEventHubInterface : public EventHubInterface {
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 08a5559..48f5673 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -112,6 +112,15 @@
 }
 
 /**
+ * Pointer should still hide if touchpad taps are already disabled
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
+    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
+}
+
+/**
  * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
  * active
  */
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 183383f..31ad569 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -185,6 +185,14 @@
     return argX == x && argY == y;
 }
 
+MATCHER_P2(WithCursorPosition, x, y, "InputEvent with specified cursor position") {
+    const auto argX = arg.xCursorPosition;
+    const auto argY = arg.yCursorPosition;
+    *result_listener << "expected cursor position (" << x << ", " << y << "), but got (" << argX
+                     << ", " << argY << ")";
+    return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
+}
+
 MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
     const auto argX = arg.pointerCoords[pointer].getX();
     const auto argY = arg.pointerCoords[pointer].getY();
diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
index e9016bb..219b662 100644
--- a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
@@ -50,8 +50,9 @@
                     // Pops blocks if it is empty, so only pop up to num elements inserted.
                     size_t numPops = fdp.ConsumeIntegralInRange<size_t>(0, filled);
                     for (size_t i = 0; i < numPops; i++) {
-                        queue.popWithTimeout(
-                                std::chrono::nanoseconds{fdp.ConsumeIntegral<int64_t>()});
+                        // Provide a random timeout up to 1 second
+                        queue.popWithTimeout(std::chrono::nanoseconds(
+                                fdp.ConsumeIntegralInRange<int64_t>(0, 1E9)));
                     }
                     filled > numPops ? filled -= numPops : filled = 0;
                 },
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index f8ebc97..3b3ed9b 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -73,9 +73,11 @@
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyKeyArgs
-                    const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
-                    const nsecs_t readTime =
-                            eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
+                    const nsecs_t eventTime =
+                            fdp.ConsumeIntegralInRange<nsecs_t>(0,
+                                                                systemTime(SYSTEM_TIME_MONOTONIC));
+                    const nsecs_t readTime = fdp.ConsumeIntegralInRange<
+                            nsecs_t>(eventTime, std::numeric_limits<nsecs_t>::max());
                     mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
                                             eventTime, readTime,
                                             /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index a73c511..5890050 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3183,8 +3183,7 @@
     } else {
         // release sideband stream if it exists and a non null buffer is being set
         if (mDrawingState.sidebandStream != nullptr) {
-            mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
-            mDrawingState.sidebandStream = nullptr;
+            setSidebandStream(nullptr, info, postTime);
         }
     }
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 21714da..edab7ec 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -171,10 +171,8 @@
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
-                                             ResyncCallback resyncCallback,
                                              EventRegistrationFlags eventRegistration)
-      : resyncCallback(std::move(resyncCallback)),
-        mOwnerUid(callingUid),
+      : mOwnerUid(callingUid),
         mEventRegistration(eventRegistration),
         mEventThread(eventThread),
         mChannel(gui::BitTube::DefaultSize) {}
@@ -250,9 +248,7 @@
 
 EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule,
                          android::frametimeline::TokenManager* tokenManager,
-                         ThrottleVsyncCallback throttleVsyncCallback,
-                         GetVsyncPeriodFunction getVsyncPeriodFunction,
-                         std::chrono::nanoseconds workDuration,
+                         IEventThreadCallback& callback, std::chrono::nanoseconds workDuration,
                          std::chrono::nanoseconds readyDuration)
       : mThreadName(name),
         mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0),
@@ -261,11 +257,7 @@
         mVsyncSchedule(std::move(vsyncSchedule)),
         mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name),
         mTokenManager(tokenManager),
-        mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
-        mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) {
-    LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
-            "getVsyncPeriodFunction must not be null");
-
+        mCallback(callback) {
     mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
         std::unique_lock<std::mutex> lock(mMutex);
         threadMain(lock);
@@ -307,10 +299,10 @@
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
-        ResyncCallback resyncCallback, EventRegistrationFlags eventRegistration) const {
+        EventRegistrationFlags eventRegistration) const {
     return sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
                                            IPCThreadState::self()->getCallingUid(),
-                                           std::move(resyncCallback), eventRegistration);
+                                           eventRegistration);
 }
 
 status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -353,9 +345,7 @@
 }
 
 void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
-    if (connection->resyncCallback) {
-        connection->resyncCallback();
-    }
+    mCallback.resync();
 
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -371,20 +361,18 @@
         const sp<EventThreadConnection>& connection) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
-    if (connection->resyncCallback) {
-        connection->resyncCallback();
-    }
+    mCallback.resync();
 
     VsyncEventData vsyncEventData;
-    nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
-    vsyncEventData.frameInterval = frameInterval;
+    const Period frameInterval = mCallback.getVsyncPeriod(connection->mOwnerUid);
+    vsyncEventData.frameInterval = frameInterval.ns();
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
                 systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
                           presentTime, deadline);
     return vsyncEventData;
 }
@@ -549,9 +537,9 @@
                                             connection->frameRate);
         }
 
-        return mThrottleVsyncCallback &&
-                mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
-                                       connection->mOwnerUid);
+        const auto expectedPresentTime =
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
+        return mCallback.throttleVsync(expectedPresentTime, connection->mOwnerUid);
     };
 
     switch (event.header.type) {
@@ -671,9 +659,9 @@
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
         if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-            const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
-            copy.vsync.vsyncData.frameInterval = frameInterval;
-            generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp,
+            const Period frameInterval = mCallback.getVsyncPeriod(consumer->mOwnerUid);
+            copy.vsync.vsyncData.frameInterval = frameInterval.ns();
+            generateFrameTimeline(copy.vsync.vsyncData, frameInterval.ns(), copy.header.timestamp,
                                   event.vsync.vsyncData.preferredExpectedPresentationTime(),
                                   event.vsync.vsyncData.preferredDeadlineTimestamp());
         }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 576910e..a7c8b74 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -54,7 +54,6 @@
 
 // ---------------------------------------------------------------------------
 
-using ResyncCallback = std::function<void()>;
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
 enum class VSyncRequest {
@@ -69,7 +68,7 @@
 
 class EventThreadConnection : public gui::BnDisplayEventConnection {
 public:
-    EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
+    EventThreadConnection(EventThread*, uid_t callingUid,
                           EventRegistrationFlags eventRegistration = {});
     virtual ~EventThreadConnection();
 
@@ -80,9 +79,6 @@
     binder::Status requestNextVsync() override; // asynchronous
     binder::Status getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) override;
 
-    // Called in response to requestNextVsync.
-    const ResyncCallback resyncCallback;
-
     VSyncRequest vsyncRequest = VSyncRequest::None;
     const uid_t mOwnerUid;
     const EventRegistrationFlags mEventRegistration;
@@ -104,7 +100,7 @@
     virtual ~EventThread();
 
     virtual sp<EventThreadConnection> createEventConnection(
-            ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0;
+            EventRegistrationFlags eventRegistration = {}) const = 0;
 
     // Feed clients with fake VSYNC, e.g. while the display is off.
     virtual void enableSyntheticVsync(bool) = 0;
@@ -136,20 +132,25 @@
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 };
 
+struct IEventThreadCallback {
+    virtual ~IEventThreadCallback() = default;
+
+    virtual bool throttleVsync(TimePoint, uid_t) = 0;
+    virtual Period getVsyncPeriod(uid_t) = 0;
+    virtual void resync() = 0;
+};
+
 namespace impl {
 
 class EventThread : public android::EventThread {
 public:
-    using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
-    using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
-
     EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>,
-                frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction,
+                frametimeline::TokenManager*, IEventThreadCallback& callback,
                 std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
     ~EventThread();
 
     sp<EventThreadConnection> createEventConnection(
-            ResyncCallback, EventRegistrationFlags eventRegistration = {}) const override;
+            EventRegistrationFlags eventRegistration = {}) const override;
 
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
@@ -214,8 +215,7 @@
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
 
-    const ThrottleVsyncCallback mThrottleVsyncCallback;
-    const GetVsyncPeriodFunction mGetVsyncPeriodFunction;
+    IEventThreadCallback& mCallback;
 
     std::thread mThread;
     mutable std::mutex mMutex;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index aa24f56..68e2ce9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -211,6 +211,17 @@
         targeters.try_emplace(id, &targeter);
     }
 
+    if (flagutils::vrrConfigEnabled() &&
+        CC_UNLIKELY(mPacesetterFrameDurationFractionToSkip > 0.f)) {
+        const auto period = pacesetterTargeter.target().expectedFrameDuration();
+        const auto skipDuration = Duration::fromNs(
+                static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
+        ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+                      mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+        std::this_thread::sleep_for(skipDuration);
+        mPacesetterFrameDurationFractionToSkip = 0.f;
+    }
+
     const auto resultsPerDisplay = compositor.composite(pacesetterId, targeters);
     compositor.sample();
 
@@ -242,36 +253,35 @@
     return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate);
 }
 
-impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
-    return [this](nsecs_t expectedVsyncTime, uid_t uid) {
-        return !isVsyncValid(TimePoint::fromNs(expectedVsyncTime), uid);
-    };
+bool Scheduler::throttleVsync(android::TimePoint expectedPresentTime, uid_t uid) {
+    return !isVsyncValid(expectedPresentTime, uid);
 }
 
-impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
-    return [this](uid_t uid) {
-        const auto [refreshRate, period] = [this] {
-            std::scoped_lock lock(mDisplayLock);
-            const auto pacesetterOpt = pacesetterDisplayLocked();
-            LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
-            const Display& pacesetter = *pacesetterOpt;
-            return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
-                                  pacesetter.schedulePtr->period());
-        }();
+Period Scheduler::getVsyncPeriod(uid_t uid) {
+    const auto [refreshRate, period] = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
+        const Display& pacesetter = *pacesetterOpt;
+        return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
+                              pacesetter.schedulePtr->period());
+    }();
 
-        const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
+    const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
 
-        const auto frameRate = getFrameRateOverride(uid);
-        if (!frameRate.has_value()) {
-            return currentPeriod.ns();
-        }
+    const auto frameRate = getFrameRateOverride(uid);
+    if (!frameRate.has_value()) {
+        return currentPeriod;
+    }
 
-        const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
-        if (divisor <= 1) {
-            return currentPeriod.ns();
-        }
-        return currentPeriod.ns() * divisor;
-    };
+    const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
+    if (divisor <= 1) {
+        return currentPeriod;
+    }
+
+    // TODO(b/299378819): the casting is not needed, but we need a flag as it might change
+    // behaviour.
+    return Period::fromNs(currentPeriod.ns() * divisor);
 }
 
 ConnectionHandle Scheduler::createEventThread(Cycle cycle,
@@ -279,9 +289,7 @@
                                               std::chrono::nanoseconds workDuration,
                                               std::chrono::nanoseconds readyDuration) {
     auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
-                                                           getVsyncSchedule(), tokenManager,
-                                                           makeThrottleVsyncCallback(),
-                                                           makeGetVsyncPeriodFunction(),
+                                                           getVsyncSchedule(), tokenManager, *this,
                                                            workDuration, readyDuration);
 
     auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
@@ -293,7 +301,7 @@
     const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
     ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
 
-    auto connection = eventThread->createEventConnection([&] { resync(); });
+    auto connection = eventThread->createEventConnection();
 
     std::lock_guard<std::mutex> lock(mConnectionsLock);
     mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
@@ -307,8 +315,7 @@
         std::scoped_lock lock(mConnectionsLock);
         RETURN_IF_INVALID_HANDLE(handle, nullptr);
 
-        return mConnections[handle].thread->createEventConnection([&] { resync(); },
-                                                                  eventRegistration);
+        return mConnections[handle].thread->createEventConnection(eventRegistration);
     }();
     const auto layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 822f7cc..f652bb2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -98,7 +98,7 @@
 
 class VsyncSchedule;
 
-class Scheduler : android::impl::MessageQueue {
+class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
 public:
@@ -217,7 +217,6 @@
         ftl::FakeGuard guard(kMainThreadContext);
         resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
     }
-    void resync() EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. Returns true if
@@ -324,6 +323,11 @@
         return mFeatures.test(Feature::kSmallDirtyContentDetection);
     }
 
+    // Injects a delay that is a fraction of the predicted frame duration for the next frame.
+    void injectPacesetterDelay(float frameDurationFraction) REQUIRES(kMainThreadContext) {
+        mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
+    }
+
 private:
     friend class TestableScheduler;
 
@@ -420,8 +424,10 @@
 
     void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
 
-    android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const;
-    android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
+    // IEventThreadCallback overrides
+    bool throttleVsync(TimePoint, uid_t) override;
+    Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
+    void resync() override EXCLUDES(mDisplayLock);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -451,6 +457,9 @@
     // Timer used to monitor display power mode.
     ftl::Optional<OneShotTimer> mDisplayPowerTimer;
 
+    // Injected delay prior to compositing, for simulating jank.
+    float mPacesetterFrameDurationFractionToSkip GUARDED_BY(kMainThreadContext) = 0.f;
+
     ISchedulerCallback& mSchedulerCallback;
 
     // mDisplayLock may be locked while under mPolicyLock.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b7d2f9a..12aacad 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -6660,9 +6660,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1044 are currently used for backdoors. The code
+    // Numbers from 1000 to 1045 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1044) {
+    if (code >= 1000 && code <= 1045) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -7151,6 +7151,39 @@
                 }
                 return NO_ERROR;
             }
+            // Inject jank
+            // First argument is a float that describes the fraction of frame duration to jank by.
+            // Second argument is a delay in ms for triggering the jank. This is useful for working
+            // with tools that steal the adb connection. This argument is optional.
+            case 1045: {
+                if (flagutils::vrrConfigEnabled()) {
+                    float jankAmount = data.readFloat();
+                    int32_t jankDelayMs = 0;
+                    if (data.readInt32(&jankDelayMs) != NO_ERROR) {
+                        jankDelayMs = 0;
+                    }
+
+                    const auto jankDelayDuration = Duration(std::chrono::milliseconds(jankDelayMs));
+
+                    const bool jankAmountValid = jankAmount > 0.0 && jankAmount < 100.0;
+
+                    if (!jankAmountValid) {
+                        ALOGD("Ignoring invalid jank amount: %f", jankAmount);
+                        reply->writeInt32(BAD_VALUE);
+                        return BAD_VALUE;
+                    }
+
+                    (void)mScheduler->scheduleDelayed(
+                            [&, jankAmount]() FTL_FAKE_GUARD(kMainThreadContext) {
+                                mScheduler->injectPacesetterDelay(jankAmount);
+                                scheduleComposite(FrameHint::kActive);
+                            },
+                            jankDelayDuration.ns());
+                    reply->writeInt32(NO_ERROR);
+                    return NO_ERROR;
+                }
+                return err;
+            }
         }
     }
     return err;
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 5512734..6f53d62 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/native/libs/gui"
+    },
+    {
+      "path": "frameworks/native/services/inputflinger"
     }
   ],
   "presubmit": [
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 7062a4e..effbfdb 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -56,31 +56,35 @@
         ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
         sp<IBinder> asBinder = IInterface::asBinder(listener);
         asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
-        mWindowInfosListeners.erase(asBinder);
+        eraseListenerAndAckMessages(asBinder);
     }});
 }
 
 void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
         ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
-        auto it = mWindowInfosListeners.find(who);
-        int64_t listenerId = it->second.first;
-        mWindowInfosListeners.erase(who);
-
-        std::vector<int64_t> vsyncIds;
-        for (auto& [vsyncId, state] : mUnackedState) {
-            if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
-                          listenerId) != state.unackedListenerIds.end()) {
-                vsyncIds.push_back(vsyncId);
-            }
-        }
-
-        for (int64_t vsyncId : vsyncIds) {
-            ackWindowInfosReceived(vsyncId, listenerId);
-        }
+        eraseListenerAndAckMessages(who);
     }});
 }
 
+void WindowInfosListenerInvoker::eraseListenerAndAckMessages(const wp<IBinder>& binder) {
+    auto it = mWindowInfosListeners.find(binder);
+    int64_t listenerId = it->second.first;
+    mWindowInfosListeners.erase(binder);
+
+    std::vector<int64_t> vsyncIds;
+    for (auto& [vsyncId, state] : mUnackedState) {
+        if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
+                      listenerId) != state.unackedListenerIds.end()) {
+            vsyncIds.push_back(vsyncId);
+        }
+    }
+
+    for (int64_t vsyncId : vsyncIds) {
+        ackWindowInfosReceived(vsyncId, listenerId);
+    }
+}
+
 void WindowInfosListenerInvoker::windowInfosChanged(
         gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners,
         bool forceImmediateCall) {
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index f36b0ed..261fd0f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -67,6 +67,7 @@
 
     std::optional<gui::WindowInfosUpdate> mDelayedUpdate;
     WindowInfosReportedListenerSet mReportedListeners;
+    void eraseListenerAndAckMessages(const wp<IBinder>&);
 
     struct UnackedState {
         ftl::SmallVector<int64_t, kStaticCapacity> unackedListenerIds;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index cbbfa16..2f5a360 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -97,21 +97,26 @@
     return displayId;
 }
 
+struct EventThreadCallback : public IEventThreadCallback {
+    bool throttleVsync(TimePoint, uid_t) override { return false; }
+    Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
+    void resync() override {}
+};
+
 void SchedulerFuzzer::fuzzEventThread() {
     mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
             new scheduler::VsyncSchedule(getPhysicalDisplayId(),
                                          std::make_shared<mock::VSyncTracker>(),
                                          std::make_shared<mock::VSyncDispatch>(), nullptr));
-    const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
+    EventThreadCallback callback;
     std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
-            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
+            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, callback,
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
 
     thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
     sp<EventThreadConnection> connection =
-            sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>(),
-                                            nullptr);
+            sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>());
     thread->requestNextVsync(connection);
     thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
 
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index bfc03aa..d4ab786 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -11,8 +11,8 @@
 flag {
   name: "connected_display"
   namespace: "core_graphics"
-  description: "Controls SurfaceFlinger support for Connected Displays"
-  bug: "278199093"
+  description: "Controls SurfaceFlinger support for Connected Displays in 24Q1"
+  bug: "299486625"
   is_fixed_read_only: true
 }
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index e32cf88..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -67,15 +67,13 @@
 
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(mEventThread,
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+            .WillOnce(Return(
+                    sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
 
     EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
                             std::make_shared<mock::VSyncTracker>(),
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 9b46009..8891c06 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -57,7 +57,7 @@
 
 } // namespace
 
-class EventThreadTest : public testing::Test {
+class EventThreadTest : public testing::Test, public IEventThreadCallback {
 protected:
     static constexpr std::chrono::nanoseconds kWorkDuration = 0ms;
     static constexpr std::chrono::nanoseconds kReadyDuration = 3ms;
@@ -65,10 +65,8 @@
     class MockEventThreadConnection : public EventThreadConnection {
     public:
         MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
-                                  ResyncCallback&& resyncCallback,
                                   EventRegistrationFlags eventRegistration)
-              : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
-                                      eventRegistration) {}
+              : EventThreadConnection(eventThread, callingUid, eventRegistration) {}
         MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
     };
 
@@ -78,7 +76,14 @@
     EventThreadTest();
     ~EventThreadTest() override;
 
-    void setupEventThread(std::chrono::nanoseconds vsyncPeriod);
+    void SetUp() override { mVsyncPeriod = VSYNC_PERIOD; }
+
+    // IEventThreadCallback overrides
+    bool throttleVsync(TimePoint, uid_t) override;
+    Period getVsyncPeriod(uid_t) override;
+    void resync() override;
+
+    void setupEventThread();
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
                                                    EventRegistrationFlags eventRegistration = {},
                                                    uid_t ownerUid = mConnectionUid);
@@ -92,8 +97,7 @@
     void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
     void expectVsyncEventFrameTimelinesCorrect(
             nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData);
-    void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData,
-                                                       std::chrono::nanoseconds vsyncPeriod);
+    void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData);
     void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                 bool expectedConnected);
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -133,6 +137,8 @@
     sp<MockEventThreadConnection> mThrottledConnection;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
 
+    std::chrono::nanoseconds mVsyncPeriod;
+
     static constexpr uid_t mConnectionUid = 443;
     static constexpr uid_t mThrottledConnectionUid = 177;
 };
@@ -168,17 +174,24 @@
     EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
 }
 
-void EventThreadTest::setupEventThread(std::chrono::nanoseconds vsyncPeriod) {
-    const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
-        mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
-        return (uid == mThrottledConnectionUid);
-    };
-    const auto getVsyncPeriod = [vsyncPeriod](uid_t uid) { return vsyncPeriod.count(); };
+bool EventThreadTest::throttleVsync(android::TimePoint expectedVsyncTimestamp, uid_t uid) {
+    mThrottleVsyncCallRecorder.recordCall(expectedVsyncTimestamp.ns(), uid);
+    return (uid == mThrottledConnectionUid);
+}
 
+Period EventThreadTest::getVsyncPeriod(uid_t) {
+    return mVsyncPeriod;
+}
+
+void EventThreadTest::resync() {
+    mResyncCallRecorder.recordCall();
+}
+
+void EventThreadTest::setupEventThread() {
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
-                                                  mTokenManager.get(), throttleVsync,
-                                                  getVsyncPeriod, kWorkDuration, kReadyDuration);
+                                                  mTokenManager.get(), *this, kWorkDuration,
+                                                  kReadyDuration);
 
     // EventThread should register itself as VSyncSource callback.
     EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
@@ -200,9 +213,7 @@
         ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration,
         uid_t ownerUid) {
     sp<MockEventThreadConnection> connection =
-            sp<MockEventThreadConnection>::make(mThread.get(), ownerUid,
-                                                mResyncCallRecorder.getInvocable(),
-                                                eventRegistration);
+            sp<MockEventThreadConnection>::make(mThread.get(), ownerUid, eventRegistration);
     EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
     return connection;
 }
@@ -292,10 +303,9 @@
     }
 }
 
-void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(
-        VsyncEventData vsyncEventData, std::chrono::nanoseconds vsyncPeriod) {
+void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData) {
     float nonPreferredTimelinesAmount =
-            scheduler::VsyncConfig::kEarlyLatchMaxThreshold / vsyncPeriod;
+            scheduler::VsyncConfig::kEarlyLatchMaxThreshold / mVsyncPeriod;
     EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1)
             << "Amount of non-preferred frame timelines too many;"
             << " expected presentation time will be over threshold";
@@ -357,7 +367,7 @@
  */
 
 TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
     EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
@@ -368,7 +378,7 @@
 }
 
 TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
@@ -381,7 +391,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -414,7 +424,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -428,12 +438,12 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) {
+    setupEventThread();
     // The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines,
     // due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal
     // truncation (e.g. 60Hz has 16.6... ms vsync period).
-    std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
-                                         (VsyncEventData::kFrameTimelinesCapacity - 2));
-    setupEventThread(vsyncPeriod);
+    mVsyncPeriod = (scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
+                    (VsyncEventData::kFrameTimelinesCapacity - 2));
 
     // Signal that we want the next vsync event to be posted to the connection
     mThread->requestNextVsync(mConnection);
@@ -449,11 +459,11 @@
     ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
                                   << expectedTimestamp;
     const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData;
-    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, vsyncPeriod);
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
 }
 
 TEST_F(EventThreadTest, getLatestVsyncEventData) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const nsecs_t now = systemTime();
     const nsecs_t preferredExpectedPresentationTime = now + 20000000;
@@ -469,7 +479,7 @@
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
 
-    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, VSYNC_PERIOD);
+    expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
     EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
             << "Deadline timestamp should be greater than frame time";
     for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
@@ -508,7 +518,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Create a first connection, register it, and request a vsync rate of zero.
     ConnectionEventRecorder firstConnectionEventRecorder{0};
@@ -537,7 +547,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(1, mConnection);
 
@@ -562,7 +572,7 @@
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(2, mConnection);
 
@@ -590,7 +600,7 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setVsyncRate(1, mConnection);
 
@@ -609,7 +619,7 @@
 }
 
 TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -635,7 +645,7 @@
 }
 
 TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
     sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -661,42 +671,42 @@
 }
 
 TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->setDuration(321ns, 456ns);
     expectVSyncSetDurationCallReceived(321ns, 456ns);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalDisconnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugInternalConnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false);
 }
 
 TEST_F(EventThreadTest, postHotplugExternalConnect) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true);
     expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(INTERNAL_DISPLAY_ID)
@@ -710,7 +720,7 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(EXTERNAL_DISPLAY_ID)
@@ -724,7 +734,7 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setPhysicalDisplayId(DISPLAY_ID_64BIT)
@@ -737,7 +747,7 @@
 }
 
 TEST_F(EventThreadTest, suppressConfigChanged) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     ConnectionEventRecorder suppressConnectionEventRecorder{0};
     sp<MockEventThreadConnection> suppressConnection =
@@ -758,7 +768,7 @@
 }
 
 TEST_F(EventThreadTest, postUidFrameRateMapping) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
@@ -771,7 +781,7 @@
 }
 
 TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     const std::vector<FrameRateOverride> overrides = {
             {.uid = 1, .frameRateHz = 20},
@@ -791,7 +801,7 @@
 }
 
 TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
-    setupEventThread(VSYNC_PERIOD);
+    setupEventThread();
 
     // Signal that we want the next vsync event to be posted to the throttled connection
     mThread->requestNextVsync(mThrottledConnection);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 3200003..173f941 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -55,8 +55,7 @@
     class MockEventThreadConnection : public android::EventThreadConnection {
     public:
         explicit MockEventThreadConnection(EventThread* eventThread)
-              : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0),
-                                      ResyncCallback()) {}
+              : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0)) {}
         ~MockEventThreadConnection() = default;
 
         MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 3a5fdf9..aeac80d 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -124,14 +124,12 @@
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
             .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                             mock::EventThread::kCallingUid,
-                                                             ResyncCallback())));
+                                                             mock::EventThread::kCallingUid)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 908c9ab..dd998ba 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -281,14 +281,12 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                                 mock::EventThread::kCallingUid,
-                                                                 ResyncCallback())));
+                                                                 mock::EventThread::kCallingUid)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                                 mock::EventThread::kCallingUid,
-                                                                 ResyncCallback())));
+                                                                 mock::EventThread::kCallingUid)));
 
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
index c7b845e..cfb047c 100644
--- a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
@@ -245,4 +245,42 @@
     EXPECT_EQ(callCount, 1);
 }
 
+// Test that WindowInfosListenerInvoker#removeWindowInfosListener acks any unacked messages for
+// the removed listener.
+TEST_F(WindowInfosListenerInvokerTest, removeListenerAcks) {
+    // Don't ack in this listener to ensure there's an unacked message when the listener is later
+    // removed.
+    gui::WindowInfosListenerInfo listenerToBeRemovedInfo;
+    auto listenerToBeRemoved = sp<Listener>::make([](const gui::WindowInfosUpdate&) {});
+    mInvoker->addWindowInfosListener(listenerToBeRemoved, &listenerToBeRemovedInfo);
+
+    std::mutex mutex;
+    std::condition_variable cv;
+    int callCount = 0;
+    gui::WindowInfosListenerInfo listenerInfo;
+    mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+                                         std::scoped_lock lock{mutex};
+                                         callCount++;
+                                         cv.notify_one();
+                                         listenerInfo.windowInfosPublisher
+                                                 ->ackWindowInfosReceived(update.vsyncId,
+                                                                          listenerInfo.listenerId);
+                                     }),
+                                     &listenerInfo);
+
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+    mInvoker->removeWindowInfosListener(listenerToBeRemoved);
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+    // Verify that the second listener is called twice. If unacked messages aren't removed when the
+    // first listener is removed, this will fail.
+    {
+        std::unique_lock lock{mutex};
+        cv.wait(lock, [&]() { return callCount == 2; });
+    }
+    EXPECT_EQ(callCount, 2);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 8e782eb..866af3b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -29,8 +29,16 @@
     EventThread();
     ~EventThread() override;
 
-    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection,
-                (ResyncCallback, EventRegistrationFlags), (const, override));
+    // TODO(b/302035909): workaround otherwise gtest complains about
+    //  error: no viable conversion from
+    //  'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
+    //  tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
+    sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
+        return createEventConnection(false, flags);
+    }
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
+                (const));
+
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index c5870d4..dcef54d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -944,6 +944,11 @@
             return VK_ERROR_SURFACE_LOST_KHR;
         }
 
+        // Additional buffer count over min_undequeued_buffers in vulkan came from 2 total
+        // being technically enough for fifo (although a poor experience) vs 3 being the
+        // absolute minimum for mailbox to be useful. So min_undequeued_buffers + 2 is sensible
+        static constexpr int default_additional_buffers = 2;
+
         if(pPresentMode != nullptr) {
             switch (pPresentMode->presentMode) {
                 case VK_PRESENT_MODE_IMMEDIATE_KHR:
@@ -951,8 +956,8 @@
                     break;
                 case VK_PRESENT_MODE_MAILBOX_KHR:
                 case VK_PRESENT_MODE_FIFO_KHR:
-                    capabilities->minImageCount =
-                        std::min(max_buffer_count, min_undequeued_buffers + 2);
+                    capabilities->minImageCount = std::min(max_buffer_count,
+                            min_undequeued_buffers + default_additional_buffers);
                     capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
                     break;
                 case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
@@ -971,7 +976,8 @@
                     break;
             }
         } else {
-            capabilities->minImageCount = std::min(max_buffer_count, min_undequeued_buffers + 2);
+            capabilities->minImageCount = std::min(max_buffer_count,
+                    min_undequeued_buffers + default_additional_buffers);
             capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
         }
     }