Merge "Harden construction sites of android::StrongPointer." into main
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index 6d14d56..277300d 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -772,8 +772,8 @@
             break;
 
             case 'i':
-                displayId = DisplayId::fromValue<PhysicalDisplayId>(atoll(optarg));
-                if (!displayId) {
+                displayId = PhysicalDisplayId::fromValue(atoll(optarg));
+                if (std::find(ids.begin(), ids.end(), displayId) == ids.end()) {
                     fprintf(stderr, "Invalid display ID: %s.\n", optarg);
                     exit(4);
                 }
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index b7ad331..5690e2f 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -23,6 +23,9 @@
 TARGET_SLOT="$1"
 STATUS_FD="$2"
 
+# "1" if the script is triggered by the `UpdateEngine.triggerPostinstall` API. Empty otherwise.
+TRIGGERED_BY_API="$3"
+
 # Maximum number of packages/steps.
 MAXIMUM_PACKAGES=1000
 
@@ -53,25 +56,43 @@
 # A source that infinitely emits arbitrary lines.
 # When connected to STDIN of another process, this source keeps STDIN open until
 # the consumer process closes STDIN or this script dies.
+# In practice, the pm command keeps consuming STDIN, so we don't need to worry
+# about running out of buffer space.
 function infinite_source {
   while echo .; do
     sleep 1
   done
 }
 
+if [[ "$TRIGGERED_BY_API" = "1" ]]; then
+  # During OTA installation, the script is called the first time, and
+  # `TRIGGERED_BY_API` can never be "1". `TRIGGERED_BY_API` being "1" means this
+  # is the second call to this script, through the
+  # `UpdateEngine.triggerPostinstall` API.
+  # When we reach here, it means Pre-reboot Dexopt is enabled in asynchronous
+  # mode and the job scheduler determined that it's the time to run the job.
+  # Start Pre-reboot Dexopt now and wait for it to finish.
+  infinite_source | pm art on-ota-staged --start
+  exit $?
+fi
+
 PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)"
 if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 3 )); then
   # Delegate to Pre-reboot Dexopt, a feature of ART Service.
   # ART Service decides what to do with this request:
   # - If Pre-reboot Dexopt is disabled or unsupported, the command returns
-  #   non-zero. This is always the case if the current system is Android 14 or
-  #   earlier.
+  #   non-zero.
+  #   This is always the case if the current system is Android 14 or earlier.
   # - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks
-  #   until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds or
-  #   not. This is the default behavior if the current system is Android 15.
-  # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command schedules
-  #   an asynchronous job and returns 0 immediately. The job will then run by the
-  #   job scheduler when the device is idle and charging.
+  #   until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds
+  #   or not.
+  #   This is the default behavior if the current system is Android 15.
+  # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command
+  #   schedules an asynchronous job and returns 0 immediately.
+  #   Later, when the device is idle and charging, the job will be run by the
+  #   job scheduler. It will call this script again through the
+  #   `UpdateEngine.triggerPostinstall` API, with `TRIGGERED_BY_API` being "1".
+  #   This is always the case if the current system is Android 16 or later.
   if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
     # Handled by Pre-reboot Dexopt.
     exit 0
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
index 83d5967..96d35cd 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -234,6 +234,12 @@
   //
   bool erase(const key_type& key) { return erase(key, begin()); }
 
+  // Removes a mapping.
+  //
+  // The last() and end() iterators, as well as those to the erased mapping, are invalidated.
+  //
+  void erase(iterator it) { map_.unstable_erase(it); }
+
   // Removes all mappings.
   //
   // All iterators are invalidated.
diff --git a/include/input/BlockingQueue.h b/include/input/BlockingQueue.h
index f848c82..6e32de6 100644
--- a/include/input/BlockingQueue.h
+++ b/include/input/BlockingQueue.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <input/PrintTools.h>
 #include <condition_variable>
 #include <functional>
 #include <list>
@@ -126,11 +127,21 @@
      * Primary used for debugging.
      * Does not block.
      */
-    size_t size() {
+    size_t size() const {
         std::scoped_lock lock(mLock);
         return mQueue.size();
     }
 
+    bool empty() const {
+        std::scoped_lock lock(mLock);
+        return mQueue.empty();
+    }
+
+    std::string dump(std::string (*toString)(const T&) = constToString) const {
+        std::scoped_lock lock(mLock);
+        return dumpContainer(mQueue, toString);
+    }
+
 private:
     const std::optional<size_t> mCapacity;
     /**
@@ -140,7 +151,7 @@
     /**
      * Lock for accessing and waiting on elements.
      */
-    std::mutex mLock;
+    mutable std::mutex mLock;
     std::list<T> mQueue GUARDED_BY(mLock);
 };
 
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 152c815..83f4719 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -147,9 +147,11 @@
          data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
          data.writeInt32(uid);
          data.writeString16(callingPackage);
-         remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
+         status_t err = remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
          // fail on exception
-         if (reply.readExceptionCode() != 0) return false;
+         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+             return false;
+         }
          return reply.readInt32() == 1;
     }
 
@@ -159,9 +161,9 @@
         data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
         data.writeInt32(uid);
         data.writeString16(callingPackage);
-        remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
+        status_t err = remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
         // fail on exception
-        if (reply.readExceptionCode() != 0) {
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
             return ActivityManager::PROCESS_STATE_UNKNOWN;
         }
         return reply.readInt32();
@@ -192,7 +194,7 @@
         data.writeInt32(appPid);
         status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
                                           IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
@@ -207,7 +209,7 @@
         data.writeInt32(appPid);
         status_t err =
                 remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
@@ -224,7 +226,7 @@
         data.writeInt32(appPid);
         status_t err = remote()->transact(LOG_FGS_API_STATE_CHANGED_TRANSACTION, data, &reply,
                                           IBinder::FLAG_ONEWAY);
-        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+        if (err != NO_ERROR) {
             ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
             return err;
         }
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index bb45ad2..993ad82 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -263,7 +263,6 @@
         "android.utils.IMemory",
         "android.utils.IMemoryHeap",
         "com.android.car.procfsinspector.IProcfsInspector",
-        "com.android.internal.app.IAppOpsCallback",
         "com.android.internal.app.IAppOpsService",
         "com.android.internal.app.IBatteryStats",
         "com.android.internal.os.IResultReceiver",
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index 089c775..8050205 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -27,6 +27,10 @@
 
 #if defined(__ANDROID_VENDOR__)
 
+#if defined(__ANDROID_PRODUCT__)
+#error "build bug: product is not part of the vendor half of the Treble system/vendor split"
+#endif
+
 /**
  * Private addition to binder_flag_t.
  */
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 4036551..46651ce 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -26,6 +26,7 @@
     ],
     visibility: [
         "//device/google/cuttlefish/shared/minidroid/sample",
+        "//hardware/interfaces/security/see:__subpackages__",
         "//packages/modules/Virtualization:__subpackages__",
     ],
     apex_available: [
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 8c0501b..6a8a698 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -1201,5 +1201,45 @@
                 Ok(v.map(|v| v.into_iter().map(Self).collect()))
             }
         }
+
+        impl std::ops::BitOr for $enum {
+            type Output = Self;
+            fn bitor(self, rhs: Self) -> Self {
+                Self(self.0 | rhs.0)
+            }
+        }
+
+        impl std::ops::BitOrAssign for $enum {
+            fn bitor_assign(&mut self, rhs: Self) {
+                self.0 = self.0 | rhs.0;
+            }
+        }
+
+        impl std::ops::BitAnd for $enum {
+            type Output = Self;
+            fn bitand(self, rhs: Self) -> Self {
+                Self(self.0 & rhs.0)
+            }
+        }
+
+        impl std::ops::BitAndAssign for $enum {
+            fn bitand_assign(&mut self, rhs: Self) {
+                self.0 = self.0 & rhs.0;
+            }
+        }
+
+        impl std::ops::BitXor for $enum {
+            type Output = Self;
+            fn bitxor(self, rhs: Self) -> Self {
+                Self(self.0 ^ rhs.0)
+            }
+        }
+
+        impl std::ops::BitXorAssign for $enum {
+            fn bitxor_assign(&mut self, rhs: Self) {
+                self.0 = self.0 ^ rhs.0;
+            }
+        }
+
     };
 }
diff --git a/libs/binder/tests/binderStabilityIntegrationTest.cpp b/libs/binder/tests/binderStabilityIntegrationTest.cpp
index a3fc9cc..cbc4180 100644
--- a/libs/binder/tests/binderStabilityIntegrationTest.cpp
+++ b/libs/binder/tests/binderStabilityIntegrationTest.cpp
@@ -47,6 +47,7 @@
 
     Stability::Level level = Stability::Level::UNDECLARED;
     switch (partition) {
+        case Partition::PRODUCT:
         case Partition::SYSTEM:
         case Partition::SYSTEM_EXT:
             level = Stability::Level::SYSTEM;
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
index a8acded..16147dd 100644
--- a/libs/debugstore/rust/src/core.rs
+++ b/libs/debugstore/rust/src/core.rs
@@ -48,7 +48,7 @@
     ///
     /// This constant is used as a part of the debug store's data format,
     /// allowing for version tracking and compatibility checks.
-    const ENCODE_VERSION: u32 = 2;
+    const ENCODE_VERSION: u32 = 3;
 
     /// Creates a new instance of `DebugStore` with specified event limit and maximum delay.
     fn new() -> Self {
@@ -123,9 +123,11 @@
 
 impl fmt::Display for DebugStore {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Write the debug store header information
         let uptime_now = uptimeMillis();
         write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?;
 
+        // Join events with a separator
         write!(
             f,
             "{}",
@@ -136,7 +138,10 @@
                 acc.push_str(&event.to_string());
                 acc
             })
-        )
+        )?;
+
+        // Write the debug store footer
+        write!(f, ";;")
     }
 }
 
diff --git a/libs/graphicsenv/FeatureOverrides.cpp b/libs/graphicsenv/FeatureOverrides.cpp
index 6974da9..51afe28 100644
--- a/libs/graphicsenv/FeatureOverrides.cpp
+++ b/libs/graphicsenv/FeatureOverrides.cpp
@@ -14,14 +14,46 @@
  * limitations under the License.
  */
 
-#include <graphicsenv/FeatureOverrides.h>
+#include <cinttypes>
 
 #include <android-base/stringprintf.h>
+#include <binder/Parcel.h>
+#include <graphicsenv/FeatureOverrides.h>
 
 namespace android {
 
 using base::StringAppendF;
 
+status_t FeatureConfig::writeToParcel(Parcel* parcel) const {
+    status_t status;
+
+    status = parcel->writeUtf8AsUtf16(mFeatureName);
+    if (status != OK) {
+        return status;
+    }
+    status = parcel->writeBool(mEnabled);
+    if (status != OK) {
+        return status;
+    }
+
+    return OK;
+}
+
+status_t FeatureConfig::readFromParcel(const Parcel* parcel) {
+    status_t status;
+
+    status = parcel->readUtf8FromUtf16(&mFeatureName);
+    if (status != OK) {
+        return status;
+    }
+    status = parcel->readBool(&mEnabled);
+    if (status != OK) {
+        return status;
+    }
+
+    return OK;
+}
+
 std::string FeatureConfig::toString() const {
     std::string result;
     StringAppendF(&result, "Feature: %s\n", mFeatureName.c_str());
@@ -30,6 +62,91 @@
     return result;
 }
 
+status_t FeatureOverrides::writeToParcel(Parcel* parcel) const {
+    status_t status;
+    // Number of global feature configs.
+    status = parcel->writeVectorSize(mGlobalFeatures);
+    if (status != OK) {
+        return status;
+    }
+    // Global feature configs.
+    for (const auto& cfg : mGlobalFeatures) {
+        status = cfg.writeToParcel(parcel);
+        if (status != OK) {
+            return status;
+        }
+    }
+    // Number of package feature overrides.
+    status = parcel->writeInt32(static_cast<int32_t>(mPackageFeatures.size()));
+    if (status != OK) {
+        return status;
+    }
+    for (const auto& feature : mPackageFeatures) {
+        // Package name.
+        status = parcel->writeUtf8AsUtf16(feature.first);
+        if (status != OK) {
+            return status;
+        }
+        // Number of package feature configs.
+        status = parcel->writeVectorSize(feature.second);
+        if (status != OK) {
+            return status;
+        }
+        // Package feature configs.
+        for (const auto& cfg : feature.second) {
+            status = cfg.writeToParcel(parcel);
+            if (status != OK) {
+                return status;
+            }
+        }
+    }
+
+    return OK;
+}
+
+status_t FeatureOverrides::readFromParcel(const Parcel* parcel) {
+    status_t status;
+
+    // Number of global feature configs.
+    status = parcel->resizeOutVector(&mGlobalFeatures);
+    if (status != OK) {
+        return status;
+    }
+    // Global feature configs.
+    for (FeatureConfig& cfg : mGlobalFeatures) {
+        status = cfg.readFromParcel(parcel);
+        if (status != OK) {
+            return status;
+        }
+    }
+
+    // Number of package feature overrides.
+    int numPkgOverrides = parcel->readInt32();
+    for (int i = 0; i < numPkgOverrides; i++) {
+        // Package name.
+        std::string name;
+        status = parcel->readUtf8FromUtf16(&name);
+        if (status != OK) {
+            return status;
+        }
+        std::vector<FeatureConfig> cfgs;
+        // Number of package feature configs.
+        int numCfgs = parcel->readInt32();
+        // Package feature configs.
+        for (int j = 0; j < numCfgs; j++) {
+            FeatureConfig cfg;
+            status = cfg.readFromParcel(parcel);
+            if (status != OK) {
+                return status;
+            }
+            cfgs.emplace_back(cfg);
+        }
+        mPackageFeatures[name] = cfgs;
+    }
+
+    return OK;
+}
+
 std::string FeatureOverrides::toString() const {
     std::string result;
     result.append("Global Features:\n");
diff --git a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
index 2b94187..450eed2 100644
--- a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
+++ b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
@@ -20,13 +20,17 @@
 #include <string>
 #include <vector>
 
+#include <binder/Parcelable.h>
+
 namespace android {
 
-class FeatureConfig {
+class FeatureConfig : public Parcelable {
 public:
     FeatureConfig() = default;
     FeatureConfig(const FeatureConfig&) = default;
     virtual ~FeatureConfig() = default;
+    virtual status_t writeToParcel(Parcel* parcel) const;
+    virtual status_t readFromParcel(const Parcel* parcel);
     std::string toString() const;
 
     std::string mFeatureName;
@@ -37,11 +41,13 @@
  * Class for transporting OpenGL ES Feature configurations from GpuService to authorized
  * recipients.
  */
-class FeatureOverrides {
+class FeatureOverrides : public Parcelable {
 public:
     FeatureOverrides() = default;
     FeatureOverrides(const FeatureOverrides&) = default;
     virtual ~FeatureOverrides() = default;
+    virtual status_t writeToParcel(Parcel* parcel) const;
+    virtual status_t readFromParcel(const Parcel* parcel);
     std::string toString() const;
 
     std::vector<FeatureConfig> mGlobalFeatures;
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 0848fac..310f781 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -222,8 +222,6 @@
     ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);
     mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);
     mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers;
-    mNumAcquired = 0;
-    mNumFrameAvailable = 0;
 
     TransactionCompletedListener::getInstance()->addQueueStallListener(
             [&](const std::string& reason) {
@@ -415,14 +413,12 @@
                                                     stat.frameEventStats.dequeueReadyTime);
                 }
                 auto currFrameNumber = stat.frameEventStats.frameNumber;
-                std::vector<ReleaseCallbackId> staleReleases;
-                for (const auto& [key, value]: mSubmitted) {
-                    if (currFrameNumber > key.framenumber) {
-                        staleReleases.push_back(key);
+                // Release stale buffers.
+                for (const auto& [key, _] : mSubmitted) {
+                    if (currFrameNumber <= key.framenumber) {
+                        continue; // not stale.
                     }
-                }
-                for (const auto& staleRelease : staleReleases) {
-                    releaseBufferCallbackLocked(staleRelease,
+                    releaseBufferCallbackLocked(key,
                                                 stat.previousReleaseFence
                                                         ? stat.previousReleaseFence
                                                         : Fence::NO_FENCE,
@@ -441,7 +437,7 @@
 
 void BLASTBufferQueue::flushShadowQueue() {
     BQA_LOGV("flushShadowQueue");
-    int numFramesToFlush = mNumFrameAvailable;
+    int32_t numFramesToFlush = mNumFrameAvailable;
     while (numFramesToFlush > 0) {
         acquireNextBufferLocked(std::nullopt);
         numFramesToFlush--;
@@ -618,7 +614,7 @@
     mNumAcquired++;
     mLastAcquiredFrameNumber = bufferItem.mFrameNumber;
     ReleaseCallbackId releaseCallbackId(buffer->getId(), mLastAcquiredFrameNumber);
-    mSubmitted[releaseCallbackId] = bufferItem;
+    mSubmitted.emplace_or_replace(releaseCallbackId, bufferItem);
 
     bool needsDisconnect = false;
     mBufferItemConsumer->getConnectionEvents(bufferItem.mFrameNumber, &needsDisconnect);
@@ -851,7 +847,7 @@
 
 void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
     std::lock_guard _lock{mTimestampMutex};
-    mDequeueTimestamps[bufferId] = systemTime();
+    mDequeueTimestamps.emplace_or_replace(bufferId, systemTime());
 };
 
 void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index ba50bf8..ab747b9 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -238,7 +238,7 @@
         // socket should be atomic across processes.
         DisplayEventReceiver::Event event;
         event.header =
-                DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL,
+                DisplayEventReceiver::Event::Header{DisplayEventType::DISPLAY_EVENT_NULL,
                                                     PhysicalDisplayId::fromPort(0), systemTime()};
         injectEvent(event);
     }
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 3ad0e52..67de742 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -385,6 +385,26 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+status_t ConsumerBase::addReleaseFence(const sp<GraphicBuffer> buffer, const sp<Fence>& fence) {
+    CB_LOGV("addReleaseFence");
+    Mutex::Autolock lock(mMutex);
+
+    if (mAbandoned) {
+        CB_LOGE("addReleaseFence: ConsumerBase is abandoned!");
+        return NO_INIT;
+    }
+    if (buffer == nullptr) {
+        return BAD_VALUE;
+    }
+
+    int slotIndex = getSlotForBufferLocked(buffer);
+    if (slotIndex == BufferQueue::INVALID_BUFFER_SLOT) {
+        return BAD_VALUE;
+    }
+
+    return addReleaseFenceLocked(slotIndex, buffer, fence);
+}
+
 status_t ConsumerBase::setDefaultBufferSize(uint32_t width, uint32_t height) {
     Mutex::Autolock _l(mMutex);
     if (mAbandoned) {
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 68f10f4..6f23885 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -167,7 +167,7 @@
         for (ssize_t i = 0; i < n; i++) {
             const DisplayEventReceiver::Event& ev = buf[i];
             switch (ev.header.type) {
-                case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+                case DisplayEventType::DISPLAY_EVENT_VSYNC:
                     // Later vsync events will just overwrite the info from earlier
                     // ones. That's fine, we only care about the most recent.
                     gotVsync = true;
@@ -183,7 +183,7 @@
                         ATRACE_INT("RenderRate", fps);
                     }
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+                case DisplayEventType::DISPLAY_EVENT_HOTPLUG:
                     if (ev.hotplug.connectionError == 0) {
                         dispatchHotplug(ev.header.timestamp, ev.header.displayId,
                                         ev.hotplug.connected);
@@ -192,31 +192,28 @@
                                                        ev.hotplug.connectionError);
                     }
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
+                case DisplayEventType::DISPLAY_EVENT_MODE_CHANGE:
                     dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
                                         ev.modeChange.modeId, ev.modeChange.vsyncPeriod);
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_NULL:
+                case DisplayEventType::DISPLAY_EVENT_NULL:
                     dispatchNullEvent(ev.header.timestamp, ev.header.displayId);
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+                case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
                     mFrameRateOverrides.emplace_back(ev.frameRateOverride);
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
+                case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
                     dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId,
                                                std::move(mFrameRateOverrides));
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+                case DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
                     dispatchHdcpLevelsChanged(ev.header.displayId,
                                               ev.hdcpLevelsChange.connectedLevel,
                                               ev.hdcpLevelsChange.maxLevel);
                     break;
-                case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+                case DisplayEventType::DISPLAY_EVENT_MODE_REJECTION:
                     dispatchModeRejected(ev.header.displayId, ev.modeRejection.modeId);
                     break;
-                default:
-                    ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
-                    break;
             }
         }
     }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 44aac9b..ebfc62f 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -67,7 +67,6 @@
         reserved(0),
         cornerRadius(0.0f),
         clientDrawnCornerRadius(0.0f),
-        clientDrawnShadowRadius(0.0f),
         backgroundBlurRadius(0),
         color(0),
         bufferTransform(0),
@@ -143,7 +142,6 @@
     SAFE_PARCEL(output.write, colorTransform.asArray(), 16 * sizeof(float));
     SAFE_PARCEL(output.writeFloat, cornerRadius);
     SAFE_PARCEL(output.writeFloat, clientDrawnCornerRadius);
-    SAFE_PARCEL(output.writeFloat, clientDrawnShadowRadius);
     SAFE_PARCEL(output.writeUint32, backgroundBlurRadius);
     SAFE_PARCEL(output.writeParcelable, metadata);
     SAFE_PARCEL(output.writeFloat, bgColor.r);
@@ -279,7 +277,6 @@
     SAFE_PARCEL(input.read, &colorTransform, 16 * sizeof(float));
     SAFE_PARCEL(input.readFloat, &cornerRadius);
     SAFE_PARCEL(input.readFloat, &clientDrawnCornerRadius);
-    SAFE_PARCEL(input.readFloat, &clientDrawnShadowRadius);
     SAFE_PARCEL(input.readUint32, &backgroundBlurRadius);
     SAFE_PARCEL(input.readParcelable, &metadata);
 
@@ -606,10 +603,6 @@
         what |= eClientDrawnCornerRadiusChanged;
         clientDrawnCornerRadius = other.clientDrawnCornerRadius;
     }
-    if (other.what & eClientDrawnShadowsChanged) {
-        what |= eClientDrawnShadowsChanged;
-        clientDrawnShadowRadius = other.clientDrawnShadowRadius;
-    }
     if (other.what & eBackgroundBlurRadiusChanged) {
         what |= eBackgroundBlurRadiusChanged;
         backgroundBlurRadius = other.backgroundBlurRadius;
@@ -824,7 +817,6 @@
     CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
     CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
     CHECK_DIFF(diff, eClientDrawnCornerRadiusChanged, other, clientDrawnCornerRadius);
-    CHECK_DIFF(diff, eClientDrawnShadowsChanged, other, clientDrawnShadowRadius);
     CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
     if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
     if (other.what & eRelativeLayerChanged) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 852885b..37ed23b 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1415,9 +1415,8 @@
             ComposerServiceAIDL::getComposerService()->getPhysicalDisplayIds(&displayIds);
     if (status.isOk()) {
         physicalDisplayIds.reserve(displayIds.size());
-        for (auto item : displayIds) {
-            auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(item));
-            physicalDisplayIds.push_back(*id);
+        for (auto id : displayIds) {
+            physicalDisplayIds.push_back(PhysicalDisplayId::fromValue(static_cast<uint64_t>(id)));
         }
     }
     return physicalDisplayIds;
@@ -1688,17 +1687,6 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setClientDrawnShadowRadius(
-        const sp<SurfaceControl>& sc, float clientDrawnShadowRadius) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-    s->what |= layer_state_t::eClientDrawnShadowsChanged;
-    s->clientDrawnShadowRadius = clientDrawnShadowRadius;
-    return *this;
-}
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackgroundBlurRadius(
         const sp<SurfaceControl>& sc, int backgroundBlurRadius) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index b97a496..cdc2150 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -20,6 +20,7 @@
 #include <optional>
 #include <queue>
 
+#include <ftl/small_map.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/IGraphicBufferConsumer.h>
@@ -36,6 +37,10 @@
 
 namespace android {
 
+// Sizes determined empirically to avoid allocations during common activity.
+constexpr size_t kSubmittedBuffersMapSizeHint = 8;
+constexpr size_t kDequeueTimestampsMapSizeHint = 32;
+
 class BLASTBufferQueue;
 class BufferItemConsumer;
 
@@ -206,7 +211,7 @@
 
     // Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
     // buffer or the buffer has been presented and a new buffer is ready to be presented.
-    std::unordered_map<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
+    ftl::SmallMap<ReleaseCallbackId, BufferItem, kSubmittedBuffersMapSizeHint> mSubmitted
             GUARDED_BY(mMutex);
 
     // Keep a queue of the released buffers instead of immediately releasing
@@ -291,8 +296,8 @@
     std::mutex mTimestampMutex;
     // Tracks buffer dequeue times by the client. This info is sent to SurfaceFlinger which uses
     // it for debugging purposes.
-    std::unordered_map<uint64_t /* bufferId */, nsecs_t> mDequeueTimestamps
-            GUARDED_BY(mTimestampMutex);
+    ftl::SmallMap<uint64_t /* bufferId */, nsecs_t, kDequeueTimestampsMapSizeHint>
+            mDequeueTimestamps GUARDED_BY(mTimestampMutex);
 
     // Keep track of SurfaceControls that have submitted a transaction and BBQ is waiting on a
     // callback for them.
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index acb0006..2e347c9 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -98,6 +98,8 @@
     status_t detachBuffer(const sp<GraphicBuffer>& buffer);
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+    status_t addReleaseFence(const sp<GraphicBuffer> buffer, const sp<Fence>& fence);
+
     // See IGraphicBufferConsumer::setDefaultBufferSize
     status_t setDefaultBufferSize(uint32_t width, uint32_t height);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index ab6a6b7..f51390a 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -55,20 +55,20 @@
         static_cast<uint32_t>(c4);
 }
 
+enum class DisplayEventType : uint32_t {
+    DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
+    DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
+    DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
+    DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'),
+    DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
+    DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
+    DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
+    DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
+};
+
 // ----------------------------------------------------------------------------
 class DisplayEventReceiver {
 public:
-    enum {
-        DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
-        DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
-        DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
-        DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'),
-        DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
-        DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
-        DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
-        DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
-    };
-
     struct Event {
         // We add __attribute__((aligned(8))) for nsecs_t fields because
         // we need to make sure all fields are aligned the same with x86
@@ -77,7 +77,7 @@
         // https://en.wikipedia.org/wiki/Data_structure_alignment
 
         struct Header {
-            uint32_t type;
+            DisplayEventType type;
             PhysicalDisplayId displayId __attribute__((aligned(8)));
             nsecs_t timestamp __attribute__((aligned(8)));
         };
diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h
index 6530b50..fb4aaa7 100644
--- a/libs/gui/include/gui/InputTransferToken.h
+++ b/libs/gui/include/gui/InputTransferToken.h
@@ -25,7 +25,7 @@
 namespace android {
 struct InputTransferToken : public RefBase, Parcelable {
 public:
-    InputTransferToken() { mToken = new BBinder(); }
+    InputTransferToken() { mToken = sp<BBinder>::make(); }
 
     InputTransferToken(const sp<IBinder>& token) { mToken = token; }
 
@@ -50,4 +50,4 @@
     return token1->mToken == token2->mToken;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 1002614..c2680a4 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -232,7 +232,6 @@
         ePictureProfileHandleChanged = 0x80000'00000000,
         eAppContentPriorityChanged = 0x100000'00000000,
         eClientDrawnCornerRadiusChanged = 0x200000'00000000,
-        eClientDrawnShadowsChanged = 0x400000'00000000,
     };
 
     layer_state_t();
@@ -276,7 +275,7 @@
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
             layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
-            layer_state_t::eClientDrawnShadowsChanged | layer_state_t::eStretchChanged |
+            layer_state_t::eStretchChanged |
             layer_state_t::ePictureProfileHandleChanged | layer_state_t::eAppContentPriorityChanged;
 
     // Changes which invalidates the layer's visible region in CE.
@@ -336,7 +335,6 @@
     matrix22_t matrix;
     float cornerRadius;
     float clientDrawnCornerRadius;
-    float clientDrawnShadowRadius;
     uint32_t backgroundBlurRadius;
 
     sp<SurfaceControl> relativeLayerSurfaceControl;
@@ -504,12 +502,18 @@
     Rect layerStackSpaceRect = Rect::EMPTY_RECT;
     Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
 
-    // Exclusive to virtual displays: The sink surface into which the virtual display is rendered,
-    // and an optional resolution that overrides its default dimensions.
-    sp<IGraphicBufferProducer> surface;
+    // For physical displays, this is the resolution, which must match the active display mode. To
+    // change the resolution, the client must first call SurfaceControl.setDesiredDisplayModeSpecs
+    // with the new DesiredDisplayModeSpecs#defaultMode, then commit the matching width and height.
+    //
+    // For virtual displays, this is an optional resolution that overrides its default dimensions.
+    //
     uint32_t width = 0;
     uint32_t height = 0;
 
+    // For virtual displays, this is the sink surface into which the virtual display is rendered.
+    sp<IGraphicBufferProducer> surface;
+
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
 };
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index d30a830..10225cc 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -569,10 +569,6 @@
         // radius is drawn by the client and not SurfaceFlinger.
         Transaction& setClientDrawnCornerRadius(const sp<SurfaceControl>& sc,
                                                 float clientDrawnCornerRadius);
-        // Sets the client drawn shadow radius for the layer. This indicates that the shadows
-        // are drawn by the client and not SurfaceFlinger.
-        Transaction& setClientDrawnShadowRadius(const sp<SurfaceControl>& sc,
-                                                float clientDrawnShadowRadius);
         Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
                                              int backgroundBlurRadius);
         Transaction& setBlurRegions(const sp<SurfaceControl>& sc,
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index a0d8c53..c35efe2 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -40,7 +40,7 @@
         std::unique_lock<decltype(mutex_)> lk(mutex_);
 
         auto check_event = [](auto const& ev) -> bool {
-            return ev.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
+            return ev.header.type == DisplayEventType::DISPLAY_EVENT_VSYNC;
         };
         DisplayEventReceiver::Event ev_;
         int evs = receiver_.getEvents(&ev_, 1);
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 4e187ed..5bb30db 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -255,3 +255,12 @@
   bug: "277261245"
 }
 
+flag {
+  name: "prevent_merging_input_pointer_devices"
+  namespace: "desktop_input"
+  description: "Prevent merging input sub-devices that provide pointer input streams"
+  bug: "389689566"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 6eb2d73..35ba04f 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -50,7 +50,7 @@
 
 bitflags! {
     /// Source of the input device or input events.
-    #[derive(Debug, PartialEq)]
+    #[derive(Clone, Copy, Debug, PartialEq)]
     pub struct Source: u32 {
         // Constants from SourceClass, added here for compatibility reasons
         /// SourceClass::Button
@@ -101,7 +101,7 @@
 
 /// A rust enum representation of a MotionEvent action.
 #[repr(u32)]
-#[derive(Eq, PartialEq)]
+#[derive(Clone, Copy, Eq, PartialEq)]
 pub enum MotionAction {
     /// ACTION_DOWN
     Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
@@ -132,9 +132,15 @@
     /// ACTION_SCROLL
     Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
     /// ACTION_BUTTON_PRESS
-    ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+    ButtonPress {
+        /// The button being pressed.
+        action_button: MotionButton,
+    } = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
     /// ACTION_BUTTON_RELEASE
-    ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+    ButtonRelease {
+        /// The button being released.
+        action_button: MotionButton,
+    } = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
 }
 
 impl fmt::Display for MotionAction {
@@ -153,14 +159,20 @@
             MotionAction::Scroll => write!(f, "SCROLL"),
             MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
             MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
-            MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
-            MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+            MotionAction::ButtonPress { action_button } => {
+                write!(f, "BUTTON_PRESS({action_button:?})")
+            }
+            MotionAction::ButtonRelease { action_button } => {
+                write!(f, "BUTTON_RELEASE({action_button:?})")
+            }
         }
     }
 }
 
-impl From<u32> for MotionAction {
-    fn from(action: u32) -> Self {
+impl MotionAction {
+    /// Creates a [`MotionAction`] from an `AMOTION_EVENT_ACTION_…` constant and an action button
+    /// (which should be empty for all actions except `BUTTON_PRESS` and `…_RELEASE`).
+    pub fn from_code(action: u32, action_button: MotionButton) -> Self {
         let (action_masked, action_index) = MotionAction::breakdown_action(action);
         match action_masked {
             input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
@@ -178,14 +190,16 @@
             input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
             input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
             input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
-            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
-            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => {
+                MotionAction::ButtonPress { action_button }
+            }
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => {
+                MotionAction::ButtonRelease { action_button }
+            }
             _ => panic!("Unknown action: {}", action),
         }
     }
-}
 
-impl MotionAction {
     fn breakdown_action(action: u32) -> (u32, usize) {
         let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
         let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
@@ -219,7 +233,7 @@
     /// MotionEvent flags.
     /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
     /// The flag values are redefined here as a bitflags API.
-    #[derive(Debug)]
+    #[derive(Clone, Copy, Debug)]
     pub struct MotionFlags: u32 {
         /// FLAG_WINDOW_IS_OBSCURED
         const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 6d94316..f87dd41 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -22,26 +22,50 @@
 use std::collections::HashMap;
 use std::collections::HashSet;
 
-fn verify_event(
-    action: MotionAction,
-    action_button: MotionButton,
-    pointer_properties: &[RustPointerProperties],
-    flags: &MotionFlags,
-    verify_buttons: bool,
-) -> Result<(), String> {
-    let pointer_count = pointer_properties.len();
+/// Represents a movement or state change event from a pointer device. The Rust equivalent of the
+/// C++ NotifyMotionArgs struct.
+#[derive(Clone, Copy)]
+pub struct NotifyMotionArgs<'a> {
+    /// The ID of the device that emitted the event.
+    pub device_id: DeviceId,
+
+    /// The type of device that emitted the event.
+    pub source: Source,
+
+    /// The type of event that took place.
+    pub action: MotionAction,
+
+    /// The properties of each of the pointers involved in the event.
+    pub pointer_properties: &'a [RustPointerProperties],
+
+    /// Flags applied to the event.
+    pub flags: MotionFlags,
+
+    /// The set of buttons that were pressed at the time of the event.
+    ///
+    /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
+    /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
+    /// for those buttons, building up to a button state matching that of the DOWN. For example, if
+    /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
+    /// this sequence:
+    ///
+    /// | Action         | Action button | Button state           |
+    /// |----------------|---------------|------------------------|
+    /// | `HOVER_EXIT`   | -             | -                      |
+    /// | `DOWN`         | -             | `PRIMARY`, `SECONDARY` |
+    /// | `BUTTON_PRESS` | `PRIMARY`     | `PRIMARY`              |
+    /// | `BUTTON_PRESS` | `SECONDARY`   | `PRIMARY`, `SECONDARY` |
+    /// | `MOVE`         | -             | `PRIMARY`, `SECONDARY` |
+    pub button_state: MotionButton,
+}
+
+/// Verifies the properties of an event that should always be true, regardless of the current state.
+fn verify_event(event: NotifyMotionArgs<'_>, verify_buttons: bool) -> Result<(), String> {
+    let pointer_count = event.pointer_properties.len();
     if pointer_count < 1 {
-        return Err(format!("Invalid {} event: no pointers", action));
+        return Err(format!("Invalid {} event: no pointers", event.action));
     }
-    if action_button != MotionButton::empty()
-        && action != MotionAction::ButtonPress
-        && action != MotionAction::ButtonRelease
-    {
-        return Err(format!(
-            "Invalid {action} event: has action button {action_button:?} but is not a button action"
-        ));
-    }
-    match action {
+    match event.action {
         MotionAction::Down
         | MotionAction::HoverEnter
         | MotionAction::HoverExit
@@ -49,32 +73,39 @@
         | MotionAction::Up => {
             if pointer_count != 1 {
                 return Err(format!(
-                    "Invalid {action} event: there are {pointer_count} pointers in the event",
+                    "Invalid {} event: there are {} pointers in the event",
+                    event.action, pointer_count
                 ));
             }
         }
 
         MotionAction::Cancel => {
-            if !flags.contains(MotionFlags::CANCELED) {
+            if !event.flags.contains(MotionFlags::CANCELED) {
                 return Err(format!(
-                    "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {flags:#?}",
+                    "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
+                    event.flags
                 ));
             }
         }
 
         MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
             if action_index >= pointer_count {
-                return Err(format!("Got {action}, but event has {pointer_count} pointer(s)"));
+                return Err(format!(
+                    "Got {}, but event has {} pointer(s)",
+                    event.action, pointer_count
+                ));
             }
         }
 
-        MotionAction::ButtonPress | MotionAction::ButtonRelease => {
+        MotionAction::ButtonPress { action_button }
+        | MotionAction::ButtonRelease { action_button } => {
             if verify_buttons {
                 let button_count = action_button.iter().count();
                 if button_count != 1 {
                     return Err(format!(
-                        "Invalid {action} event: must specify a single action button, not \
-                         {button_count} action buttons"
+                        "Invalid {} event: must specify a single action button, not {} action \
+                         buttons",
+                        event.action, button_count
                     ));
                 }
             }
@@ -92,59 +123,43 @@
     button_state: MotionButton,
 
     /// The set of "pending buttons", which were seen in the last DOWN event's button state but
-    /// for which we haven't seen BUTTON_PRESS events yet.
-    ///
-    /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
-    /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
-    /// for those buttons, building up to a button state matching that of the DOWN. For example, if
-    /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
-    /// this sequence:
-    ///
-    /// | Action         | Action button | Button state           |
-    /// |----------------|---------------|------------------------|
-    /// | `HOVER_EXIT`   | -             | -                      |
-    /// | `DOWN`         | -             | `PRIMARY`, `SECONDARY` |
-    /// | `BUTTON_PRESS` | `PRIMARY`     | `PRIMARY`              |
-    /// | `BUTTON_PRESS` | `SECONDARY`   | `PRIMARY`, `SECONDARY` |
-    /// | `MOVE`         | -             | `PRIMARY`, `SECONDARY` |
+    /// for which we haven't seen BUTTON_PRESS events yet (see [`NotifyMotionArgs::button_state`]).
     pending_buttons: MotionButton,
 }
 
 impl ButtonVerifier {
-    pub fn process_action(
-        &mut self,
-        action: MotionAction,
-        action_button: MotionButton,
-        button_state: MotionButton,
-    ) -> Result<(), String> {
+    pub fn process_event(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
         if !self.pending_buttons.is_empty() {
             // We just saw a DOWN with some additional buttons in its state, so it should be
             // immediately followed by ButtonPress events for those buttons.
-            if action != MotionAction::ButtonPress || !self.pending_buttons.contains(action_button)
-            {
-                return Err(format!(
-                    "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {} with \
-                     action button {:?}",
-                    self.pending_buttons, action, action_button
-                ));
-            } else {
-                self.pending_buttons -= action_button;
-            }
-        }
-        let expected_state = match action {
-            MotionAction::Down => {
-                if self.button_state - button_state != MotionButton::empty() {
+            match event.action {
+                MotionAction::ButtonPress { action_button }
+                    if self.pending_buttons.contains(action_button) =>
+                {
+                    self.pending_buttons -= action_button;
+                }
+                _ => {
                     return Err(format!(
-                        "DOWN event button state is missing {:?}",
-                        self.button_state - button_state
+                        "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {}",
+                        self.pending_buttons, event.action
                     ));
                 }
-                self.pending_buttons = button_state - self.button_state;
+            }
+        }
+        let expected_state = match event.action {
+            MotionAction::Down => {
+                if self.button_state - event.button_state != MotionButton::empty() {
+                    return Err(format!(
+                        "DOWN event button state is missing {:?}",
+                        self.button_state - event.button_state
+                    ));
+                }
+                self.pending_buttons = event.button_state - self.button_state;
                 // We've already checked that the state isn't missing any already-down buttons, and
                 // extra buttons are valid on DOWN actions, so bypass the expected state check.
-                button_state
+                event.button_state
             }
-            MotionAction::ButtonPress => {
+            MotionAction::ButtonPress { action_button } => {
                 if self.button_state.contains(action_button) {
                     return Err(format!(
                         "Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
@@ -152,24 +167,25 @@
                 }
                 self.button_state | action_button
             }
-            MotionAction::ButtonRelease => {
+            MotionAction::ButtonRelease { action_button } => {
                 if !self.button_state.contains(action_button) {
                     return Err(format!(
-                        "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}"
+                        "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}",
                     ));
                 }
                 self.button_state - action_button
             }
             _ => self.button_state,
         };
-        if button_state != expected_state {
+        if event.button_state != expected_state {
             return Err(format!(
-                "Expected {action} button state to be {expected_state:?}, but was {button_state:?}"
+                "Expected {} button state to be {:?}, but was {:?}",
+                event.action, expected_state, event.button_state
             ));
         }
         // DOWN events can have pending buttons, so don't update the state for them.
-        if action != MotionAction::Down {
-            self.button_state = button_state;
+        if event.action != MotionAction::Down {
+            self.button_state = event.button_state;
         }
         Ok(())
     }
@@ -205,78 +221,61 @@
 
     /// Process a pointer movement event from an InputDevice.
     /// If the event is not valid, we return an error string that describes the issue.
-    #[allow(clippy::too_many_arguments)]
-    pub fn process_movement(
-        &mut self,
-        device_id: DeviceId,
-        source: Source,
-        action: u32,
-        action_button: MotionButton,
-        pointer_properties: &[RustPointerProperties],
-        flags: MotionFlags,
-        button_state: MotionButton,
-    ) -> Result<(), String> {
-        if !source.is_from_class(SourceClass::Pointer) {
+    pub fn process_movement(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
+        if !event.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 {}",
-                MotionAction::from(action).to_string(),
-                device_id,
-                pointer_properties.len(),
-                if pointer_properties.len() == 1 { "" } else { "s" },
+                event.action,
+                event.device_id,
+                event.pointer_properties.len(),
+                if event.pointer_properties.len() == 1 { "" } else { "s" },
                 self.name
             );
         }
 
-        verify_event(
-            action.into(),
-            action_button,
-            pointer_properties,
-            &flags,
-            self.verify_buttons,
-        )?;
+        verify_event(event, self.verify_buttons)?;
 
         if self.verify_buttons {
-            self.button_verifier_by_device.entry(device_id).or_default().process_action(
-                action.into(),
-                action_button,
-                button_state,
-            )?;
+            self.button_verifier_by_device
+                .entry(event.device_id)
+                .or_default()
+                .process_event(event)?;
         }
 
-        match action.into() {
+        match event.action {
             MotionAction::Down => {
-                if self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                if self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
                     return Err(format!(
                         "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
-                        self.name, device_id, self.touching_pointer_ids_by_device
+                        self.name, event.device_id, self.touching_pointer_ids_by_device
                     ));
                 }
-                let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
-                it.insert(pointer_properties[0].id);
+                let it = self.touching_pointer_ids_by_device.entry(event.device_id).or_default();
+                it.insert(event.pointer_properties[0].id);
             }
             MotionAction::PointerDown { action_index } => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
                     return Err(format!(
                         "{}: Received POINTER_DOWN but no pointers are currently down \
                         for device {:?}",
-                        self.name, device_id
+                        self.name, event.device_id
                     ));
                 }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
-                if it.len() != pointer_properties.len() - 1 {
+                let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
+                if it.len() != event.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()
+                        event.pointer_properties.len()
                     ));
                 }
-                let pointer_id = pointer_properties[action_index].id;
+                let pointer_id = event.pointer_properties[action_index].id;
                 if it.contains(&pointer_id) {
                     return Err(format!(
                         "{}: Pointer with id={} already present found in the properties",
@@ -286,7 +285,7 @@
                 it.insert(pointer_id);
             }
             MotionAction::Move => {
-                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
                     return Err(format!(
                         "{}: ACTION_MOVE touching pointers don't match",
                         self.name
@@ -294,49 +293,49 @@
                 }
             }
             MotionAction::PointerUp { action_index } => {
-                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
                     return Err(format!(
                         "{}: ACTION_POINTER_UP touching pointers don't match",
                         self.name
                     ));
                 }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
-                let pointer_id = pointer_properties[action_index].id;
+                let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
+                let pointer_id = event.pointer_properties[action_index].id;
                 it.remove(&pointer_id);
             }
             MotionAction::Up => {
-                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
                     return Err(format!(
                         "{} Received ACTION_UP but no pointers are currently down for device {:?}",
-                        self.name, device_id
+                        self.name, event.device_id
                     ));
                 }
-                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
                 if it.len() != 1 {
                     return Err(format!(
                         "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
-                        self.name, it, device_id
+                        self.name, it, event.device_id
                     ));
                 }
-                let pointer_id = pointer_properties[0].id;
+                let pointer_id = event.pointer_properties[0].id;
                 if !it.contains(&pointer_id) {
                     return Err(format!(
                         "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
                         {:?} for device {:?}",
-                        self.name, pointer_id, it, device_id
+                        self.name, pointer_id, it, event.device_id
                     ));
                 }
-                self.touching_pointer_ids_by_device.remove(&device_id);
+                self.touching_pointer_ids_by_device.remove(&event.device_id);
             }
             MotionAction::Cancel => {
-                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
                     return Err(format!(
                         "{}: Got ACTION_CANCEL, but the pointers don't match. \
                         Existing pointers: {:?}",
                         self.name, self.touching_pointer_ids_by_device
                     ));
                 }
-                self.touching_pointer_ids_by_device.remove(&device_id);
+                self.touching_pointer_ids_by_device.remove(&event.device_id);
             }
             /*
              * The hovering protocol currently supports a single pointer only, because we do not
@@ -345,41 +344,41 @@
              * eventually supported.
              */
             MotionAction::HoverEnter => {
-                if self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+                if self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
                     return Err(format!(
                         "{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
                         {:?}",
-                        self.name, device_id, self.hovering_pointer_ids_by_device
+                        self.name, event.device_id, self.hovering_pointer_ids_by_device
                     ));
                 }
-                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
-                it.insert(pointer_properties[0].id);
+                let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
+                it.insert(event.pointer_properties[0].id);
             }
             MotionAction::HoverMove => {
                 // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
                 // If there was no prior HOVER_ENTER, just start a new hovering pointer.
-                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
-                it.insert(pointer_properties[0].id);
+                let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
+                it.insert(event.pointer_properties[0].id);
             }
             MotionAction::HoverExit => {
-                if !self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+                if !self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
                     return Err(format!(
                         "{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
-                        self.name, device_id
+                        self.name, event.device_id
                     ));
                 }
-                let pointer_id = pointer_properties[0].id;
-                let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                let pointer_id = event.pointer_properties[0].id;
+                let it = self.hovering_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
                 it.remove(&pointer_id);
 
                 if !it.is_empty() {
                     return Err(format!(
                         "{}: Removed hovering pointer {}, but pointers are still\
                                hovering for device {:?}: {:?}",
-                        self.name, pointer_id, device_id, it
+                        self.name, pointer_id, event.device_id, it
                     ));
                 }
-                self.hovering_pointer_ids_by_device.remove(&device_id);
+                self.hovering_pointer_ids_by_device.remove(&event.device_id);
             }
             _ => return Ok(()),
         }
@@ -421,11 +420,25 @@
 mod tests {
     use crate::input::MotionButton;
     use crate::input_verifier::InputVerifier;
+    use crate::input_verifier::NotifyMotionArgs;
     use crate::DeviceId;
+    use crate::MotionAction;
     use crate::MotionFlags;
     use crate::RustPointerProperties;
     use crate::Source;
 
+    const BASE_POINTER_PROPERTIES: [RustPointerProperties; 1] = [RustPointerProperties { id: 0 }];
+    const BASE_EVENT: NotifyMotionArgs = NotifyMotionArgs {
+        device_id: DeviceId(1),
+        source: Source::Touchscreen,
+        action: MotionAction::Down,
+        pointer_properties: &BASE_POINTER_PROPERTIES,
+        flags: MotionFlags::empty(),
+        button_state: MotionButton::empty(),
+    };
+    const BASE_MOUSE_EVENT: NotifyMotionArgs =
+        NotifyMotionArgs { source: Source::Mouse, ..BASE_EVENT };
+
     #[test]
     /**
      * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
@@ -436,15 +449,11 @@
         let pointer_properties =
             Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_err());
     }
 
@@ -454,37 +463,25 @@
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Move,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Up,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
     }
 
@@ -494,56 +491,38 @@
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         // POINTER 1 DOWN
         let two_pointer_properties =
             Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
-                    | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                MotionButton::empty(),
-                &two_pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::PointerDown { action_index: 1 },
+                pointer_properties: &two_pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         // POINTER 0 UP
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
-                    | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                MotionButton::empty(),
-                &two_pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::PointerUp { action_index: 0 },
+                pointer_properties: &two_pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         // ACTION_UP for pointer id=1
         let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_1_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Up,
+                pointer_properties: &pointer_1_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
     }
 
@@ -551,61 +530,40 @@
     fn multi_device_stream() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(1),
+                action: MotionAction::Down,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(1),
+                action: MotionAction::Move,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(2),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(2),
+                action: MotionAction::Down,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(2),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(2),
+                action: MotionAction::Move,
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(1),
+                action: MotionAction::Up,
+                ..BASE_EVENT
+            })
             .is_ok());
     }
 
@@ -613,28 +571,19 @@
     fn action_cancel() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                flags: MotionFlags::empty(),
+                ..BASE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::CANCELED,
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Cancel,
+                flags: MotionFlags::CANCELED,
+                ..BASE_EVENT
+            })
             .is_ok());
     }
 
@@ -642,28 +591,11 @@
     fn invalid_action_cancel() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::Down, ..BASE_EVENT })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(), // forgot to set FLAG_CANCELED
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::Cancel, ..BASE_EVENT })
             .is_err());
     }
 
@@ -671,17 +603,8 @@
     fn invalid_up() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::Up, ..BASE_EVENT })
             .is_err());
     }
 
@@ -689,53 +612,20 @@
     fn correct_hover_sequence() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
             .is_ok());
 
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverMove, ..BASE_EVENT })
             .is_ok());
 
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverExit, ..BASE_EVENT })
             .is_ok());
 
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
             .is_ok());
     }
 
@@ -743,29 +633,12 @@
     fn double_hover_enter() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
             .is_ok());
 
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
             .is_err());
     }
 
@@ -775,17 +648,13 @@
     fn relative_mouse_move() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(2),
-                Source::MouseRelative,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                device_id: DeviceId(2),
+                source: Source::MouseRelative,
+                action: MotionAction::Move,
+                ..BASE_EVENT
+            })
             .is_ok());
     }
 
@@ -796,42 +665,29 @@
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         // POINTER 1 DOWN
         let two_pointer_properties =
             Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
-                    | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                MotionButton::empty(),
-                &two_pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::PointerDown { action_index: 1 },
+                pointer_properties: &two_pointer_properties,
+                ..BASE_EVENT
+            })
             .is_ok());
         // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Touchscreen,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Move,
+                pointer_properties: &pointer_properties,
+                ..BASE_EVENT
+            })
             .is_err());
     }
 
@@ -839,17 +695,12 @@
     fn correct_button_press() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
     }
 
@@ -857,17 +708,12 @@
     fn button_press_without_action_button() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::empty() },
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -875,17 +721,14 @@
     fn button_press_with_multiple_action_buttons() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back | MotionButton::Forward,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back | MotionButton::Forward,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress {
+                    action_button: MotionButton::Back | MotionButton::Forward
+                },
+                button_state: MotionButton::Back | MotionButton::Forward,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -893,17 +736,12 @@
     fn button_press_without_action_button_in_state() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -911,64 +749,19 @@
     fn button_release_with_action_button_in_state() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_with_action_button() {
-        let mut verifier =
-            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
-            .is_err());
-    }
-
-    #[test]
-    fn nonbutton_action_with_action_button_and_state() {
-        let mut verifier =
-            InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
-        assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonRelease { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -976,28 +769,19 @@
     fn nonbutton_action_with_button_state_change() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::HoverEnter,
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::HoverMove,
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1005,39 +789,26 @@
     fn nonbutton_action_missing_button_state() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::HoverEnter,
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::HoverMove,
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1045,40 +816,27 @@
     fn up_without_button_release() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_UP,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Up,
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1086,28 +844,19 @@
     fn button_press_for_already_pressed_button() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1115,17 +864,12 @@
     fn button_release_for_unpressed_button() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonRelease { action_button: MotionButton::Back },
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1133,28 +877,19 @@
     fn correct_multiple_button_presses_without_down() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Forward,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back | MotionButton::Forward,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Forward },
+                button_state: MotionButton::Back | MotionButton::Forward,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
     }
 
@@ -1162,52 +897,35 @@
     fn correct_down_with_button_press() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                button_state: MotionButton::Primary | MotionButton::Secondary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Secondary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Secondary },
+                button_state: MotionButton::Primary | MotionButton::Secondary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
         // enough BUTTON_PRESSes were sent.
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Move,
+                button_state: MotionButton::Primary | MotionButton::Secondary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
     }
 
@@ -1215,29 +933,20 @@
     fn down_with_button_state_change_not_followed_by_button_press() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Move,
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1245,41 +954,28 @@
     fn down_with_button_state_change_not_followed_by_enough_button_presses() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary | MotionButton::Secondary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                button_state: MotionButton::Primary | MotionButton::Secondary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         // The DOWN event itself is OK, but it needs to be immediately followed by two
         // BUTTON_PRESSes, one for each button.
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Primary,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Primary,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Move,
+                button_state: MotionButton::Primary,
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 
@@ -1287,28 +983,19 @@
     fn down_missing_already_pressed_button() {
         let mut verifier =
             InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
-        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                MotionButton::Back,
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::Back,
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+                button_state: MotionButton::Back,
+                ..BASE_MOUSE_EVENT
+            })
             .is_ok());
         assert!(verifier
-            .process_movement(
-                DeviceId(1),
-                Source::Mouse,
-                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
-                MotionButton::empty(),
-                &pointer_properties,
-                MotionFlags::empty(),
-                MotionButton::empty(),
-            )
+            .process_movement(NotifyMotionArgs {
+                action: MotionAction::Down,
+                button_state: MotionButton::empty(),
+                ..BASE_MOUSE_EVENT
+            })
             .is_err());
     }
 }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 6db4356..7638559 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -27,7 +27,7 @@
     DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionButton,
     MotionFlags, Source,
 };
-pub use input_verifier::InputVerifier;
+pub use input_verifier::{InputVerifier, NotifyMotionArgs};
 pub use keyboard_classifier::KeyboardClassifier;
 
 #[cxx::bridge(namespace = "android::input")]
@@ -133,37 +133,46 @@
     flags: u32,
     button_state: u32,
 ) -> String {
-    let motion_flags = MotionFlags::from_bits(flags);
-    if motion_flags.is_none() {
+    let Some(motion_flags) = MotionFlags::from_bits(flags) else {
         panic!(
             "The conversion of flags 0x{:08x} failed, please check if some flags have not been \
             added to MotionFlags.",
             flags
         );
-    }
-    let motion_action_button = MotionButton::from_bits(action_button);
-    if motion_action_button.is_none() {
+    };
+    let Some(motion_action_button) = MotionButton::from_bits(action_button) else {
         panic!(
             "The conversion of action button 0x{action_button:08x} failed, please check if some \
              buttons need to be added to MotionButton."
         );
-    }
-    let motion_button_state = MotionButton::from_bits(button_state);
-    if motion_button_state.is_none() {
+    };
+    let Some(motion_button_state) = MotionButton::from_bits(button_state) else {
         panic!(
             "The conversion of button state 0x{button_state:08x} failed, please check if some \
              buttons need to be added to MotionButton."
         );
+    };
+    let motion_action = MotionAction::from_code(action, motion_action_button);
+    if motion_action_button != MotionButton::empty() {
+        match motion_action {
+            MotionAction::ButtonPress { action_button: _ }
+            | MotionAction::ButtonRelease { action_button: _ } => {}
+            _ => {
+                return format!(
+                    "Invalid {motion_action} event: has action button {motion_action_button:?} but \
+                     is not a button action"
+                );
+            }
+        }
     }
-    let result = verifier.process_movement(
-        DeviceId(device_id),
-        Source::from_bits(source).unwrap(),
-        action,
-        motion_action_button.unwrap(),
+    let result = verifier.process_movement(NotifyMotionArgs {
+        device_id: DeviceId(device_id),
+        source: Source::from_bits(source).unwrap(),
+        action: motion_action,
         pointer_properties,
-        motion_flags.unwrap(),
-        motion_button_state.unwrap(),
-    );
+        flags: motion_flags,
+        button_state: motion_button_state,
+    });
     match result {
         Ok(()) => "".to_string(),
         Err(e) => e,
@@ -230,3 +239,44 @@
     }
     classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap());
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::create_input_verifier;
+    use crate::process_movement;
+    use crate::RustPointerProperties;
+
+    const BASE_POINTER_PROPERTIES: [RustPointerProperties; 1] = [RustPointerProperties { id: 0 }];
+
+    #[test]
+    fn verify_nonbutton_action_with_action_button() {
+        let mut verifier = create_input_verifier("Test".to_string(), /*verify_buttons*/ true);
+        assert!(process_movement(
+            &mut verifier,
+            1,
+            input_bindgen::AINPUT_SOURCE_MOUSE,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+            input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+            &BASE_POINTER_PROPERTIES,
+            0,
+            0,
+        )
+        .contains("button action"));
+    }
+
+    #[test]
+    fn verify_nonbutton_action_with_action_button_and_button_state() {
+        let mut verifier = create_input_verifier("Test".to_string(), /*verify_buttons*/ true);
+        assert!(process_movement(
+            &mut verifier,
+            1,
+            input_bindgen::AINPUT_SOURCE_MOUSE,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+            input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+            &BASE_POINTER_PROPERTIES,
+            0,
+            input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+        )
+        .contains("button action"));
+    }
+}
diff --git a/libs/permission/Android.bp b/libs/permission/Android.bp
index 0eeca54..929f067 100644
--- a/libs/permission/Android.bp
+++ b/libs/permission/Android.bp
@@ -16,6 +16,7 @@
     double_loadable: true,
     srcs: [
         "aidl/android/content/AttributionSourceState.aidl",
+        "aidl/com/android/internal/app/IAppOpsCallback.aidl",
         "aidl/android/permission/IPermissionChecker.aidl",
     ],
 }
@@ -36,7 +37,6 @@
     ],
     srcs: [
         "AppOpsManager.cpp",
-        "IAppOpsCallback.cpp",
         "IAppOpsService.cpp",
         "android/permission/PermissionChecker.cpp",
     ],
diff --git a/libs/permission/IAppOpsCallback.cpp b/libs/permission/IAppOpsCallback.cpp
deleted file mode 100644
index 2b3f462..0000000
--- a/libs/permission/IAppOpsCallback.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AppOpsCallback"
-
-#include <binder/IAppOpsCallback.h>
-
-#include <utils/Log.h>
-#include <binder/Parcel.h>
-#include <utils/String8.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------
-
-class BpAppOpsCallback : public BpInterface<IAppOpsCallback>
-{
-public:
-    explicit BpAppOpsCallback(const sp<IBinder>& impl)
-        : BpInterface<IAppOpsCallback>(impl)
-    {
-    }
-
-    virtual void opChanged(int32_t op, const String16& packageName) {
-        Parcel data, reply;
-        data.writeInterfaceToken(IAppOpsCallback::getInterfaceDescriptor());
-        data.writeInt32(op);
-        data.writeString16(packageName);
-        remote()->transact(OP_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
-    }
-};
-
-IMPLEMENT_META_INTERFACE(AppOpsCallback, "com.android.internal.app.IAppOpsCallback")
-
-// ----------------------------------------------------------------------
-
-// NOLINTNEXTLINE(google-default-arguments)
-status_t BnAppOpsCallback::onTransact(
-    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
-    switch(code) {
-        case OP_CHANGED_TRANSACTION: {
-            CHECK_INTERFACE(IAppOpsCallback, data, reply);
-            int32_t op = data.readInt32();
-            String16 packageName;
-            (void)data.readString16(&packageName);
-            opChanged(op, packageName);
-            return NO_ERROR;
-        } break;
-        default:
-            return BBinder::onTransact(code, data, reply, flags);
-    }
-}
-
-} // namespace android
diff --git a/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl b/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl
new file mode 100644
index 0000000..36b19df
--- /dev/null
+++ b/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+oneway interface IAppOpsCallback {
+    void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+}
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index 7e179d6..a22c975 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -180,10 +180,10 @@
     void finishOp(int32_t op, int32_t uid, const String16& callingPackage,
             const std::optional<String16>& attributionTag);
     void startWatchingMode(int32_t op, const String16& packageName,
-            const sp<IAppOpsCallback>& callback);
+            const sp<com::android::internal::app::IAppOpsCallback>& callback);
     void startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
-            const sp<IAppOpsCallback>& callback);
-    void stopWatchingMode(const sp<IAppOpsCallback>& callback);
+            const sp<com::android::internal::app::IAppOpsCallback>& callback);
+    void stopWatchingMode(const sp<com::android::internal::app::IAppOpsCallback>& callback);
     int32_t permissionToOpCode(const String16& permission);
     void setCameraAudioRestriction(int32_t mode);
 
diff --git a/libs/permission/include/binder/IAppOpsCallback.h b/libs/permission/include/binder/IAppOpsCallback.h
deleted file mode 100644
index eb76f57..0000000
--- a/libs/permission/include/binder/IAppOpsCallback.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#ifndef __ANDROID_VNDK__
-
-#include <binder/IInterface.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------
-
-class IAppOpsCallback : public IInterface
-{
-public:
-    DECLARE_META_INTERFACE(AppOpsCallback)
-
-    virtual void opChanged(int32_t op, const String16& packageName) = 0;
-
-    enum {
-        OP_CHANGED_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION
-    };
-};
-
-// ----------------------------------------------------------------------
-
-class BnAppOpsCallback : public BnInterface<IAppOpsCallback>
-{
-public:
-    // NOLINTNEXTLINE(google-default-arguments)
-    virtual status_t    onTransact( uint32_t code,
-                                    const Parcel& data,
-                                    Parcel* reply,
-                                    uint32_t flags = 0);
-};
-
-// ----------------------------------------------------------------------
-
-} // namespace android
-
-#else // __ANDROID_VNDK__
-#error "This header is not visible to vendors"
-#endif // __ANDROID_VNDK__
diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h
index 918fcdb..1468fd9 100644
--- a/libs/permission/include/binder/IAppOpsService.h
+++ b/libs/permission/include/binder/IAppOpsService.h
@@ -16,7 +16,8 @@
 
 #pragma once
 
-#include <binder/IAppOpsCallback.h>
+#include <com/android/internal/app/IAppOpsCallback.h>
+#include <com/android/internal/app/BnAppOpsCallback.h>
 #include <binder/IInterface.h>
 
 #include <optional>
@@ -27,6 +28,8 @@
 
 namespace android {
 
+using IAppOpsCallback = ::com::android::internal::app::IAppOpsCallback;
+
 // ----------------------------------------------------------------------
 
 class IAppOpsService : public IInterface
diff --git a/libs/renderengine/OWNERS b/libs/renderengine/OWNERS
index 17ab29f..e296283 100644
--- a/libs/renderengine/OWNERS
+++ b/libs/renderengine/OWNERS
@@ -7,4 +7,3 @@
 lpy@google.com
 nscobie@google.com
 sallyqi@google.com
-scroggo@google.com
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 57041ee..3b0f036 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -337,17 +337,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {0.f, 0.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 1.f,
             .sourceDataspace = kDestDataSpace,
     };
@@ -370,16 +370,16 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = mat4(),
                             .boundaries = rect,
+                            .positionTransform = mat4(),
                             .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = false,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .sourceDataspace = kDestDataSpace,
     };
@@ -421,17 +421,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = mat4(),
                             .boundaries = boundary,
-                            .roundedCornersCrop = rect,
+                            .positionTransform = mat4(),
                             .roundedCornersRadius = {27.f, 27.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = false,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .alpha = 1.f,
             .sourceDataspace = kDestDataSpace,
@@ -489,17 +489,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {0.f, 0.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 1.f,
             .sourceDataspace = kBT2020DataSpace,
     };
@@ -527,17 +527,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = kScaleAsymmetric,
                             .boundaries = boundary,
-                            .roundedCornersCrop = rect,
+                            .positionTransform = kScaleAsymmetric,
                             .roundedCornersRadius = {64.1f, 64.1f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = true,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .alpha = 0.5f,
             .sourceDataspace = kBT2020DataSpace,
@@ -556,17 +556,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 0.5f,
             .sourceDataspace = kExtendedHdrDataSpce,
     };
@@ -594,17 +594,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = false}},
+                                                   .isOpaque = false,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 0.5f,
             .sourceDataspace = kOtherDataSpace,
     };
diff --git a/libs/renderengine/skia/debug/CaptureTimer.cpp b/libs/renderengine/skia/debug/CaptureTimer.cpp
index 11bcdb8..1c1ee0a 100644
--- a/libs/renderengine/skia/debug/CaptureTimer.cpp
+++ b/libs/renderengine/skia/debug/CaptureTimer.cpp
@@ -30,7 +30,7 @@
 
 void CaptureTimer::setTimeout(TimeoutCallback function, std::chrono::milliseconds delay) {
     this->clear = false;
-    CommonPool::post([=]() {
+    CommonPool::post([=,this]() {
         if (this->clear) return;
         std::this_thread::sleep_for(delay);
         if (this->clear) return;
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index 897f5cf..ff96b08 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -39,12 +39,36 @@
 namespace skia {
 
 KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
-    // A shader to sample each vertex of a unit regular heptagon
-    // plus the original fragment coordinate.
-    SkString blurString(R"(
+    // A shader to sample each vertex of a square, plus the original fragment coordinate,
+    // using a total of 5 samples.
+    SkString lowSampleBlurString(R"(
         uniform shader child;
         uniform float in_blurOffset;
         uniform float in_crossFade;
+        uniform float in_weightedCrossFade;
+
+        const float2 STEP_0 = float2( 0.707106781, 0.707106781);
+        const float2 STEP_1 = float2( 0.707106781, -0.707106781);
+        const float2 STEP_2 = float2(-0.707106781, -0.707106781);
+        const float2 STEP_3 = float2(-0.707106781, 0.707106781);
+
+        half4 main(float2 xy) {
+            half3 c = child.eval(xy).rgb;
+
+            c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
+
+            return half4(c * in_weightedCrossFade, in_crossFade);
+        }
+    )");
+
+    // A shader to sample each vertex of a unit regular heptagon, plus the original fragment
+    // coordinate, using a total of 8 samples.
+    SkString highSampleBlurString(R"(
+        uniform shader child;
+        uniform float in_blurOffset;
 
         const float2 STEP_0 = float2( 1.0, 0.0);
         const float2 STEP_1 = float2( 0.623489802,  0.781831482);
@@ -65,39 +89,46 @@
             c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
             c += child.eval(xy + STEP_6 * in_blurOffset).rgb;
 
-            return half4(c * 0.125 * in_crossFade, in_crossFade);
+            return half4(c * 0.125, 1.0);
         }
     )");
 
-    auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
-    LOG_ALWAYS_FATAL_IF(!blurEffect, "RuntimeShader error: %s", error.c_str());
-    mBlurEffect = std::move(blurEffect);
+    auto [lowSampleBlurEffect, error] = SkRuntimeEffect::MakeForShader(lowSampleBlurString);
+    auto [highSampleBlurEffect, error2] = SkRuntimeEffect::MakeForShader(highSampleBlurString);
+    LOG_ALWAYS_FATAL_IF(!lowSampleBlurEffect, "RuntimeShader error: %s", error.c_str());
+    LOG_ALWAYS_FATAL_IF(!highSampleBlurEffect, "RuntimeShader error: %s", error2.c_str());
+    mLowSampleBlurEffect = std::move(lowSampleBlurEffect);
+    mHighSampleBlurEffect = std::move(highSampleBlurEffect);
 }
 
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface,
                                     const sk_sp<SkImage>& readImage, const float radius,
-                                    const float alpha) const {
+                                    const float alpha,
+                                    const sk_sp<SkRuntimeEffect>& blurEffect) const {
     const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
     SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
     blurInto(drawSurface,
              readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                    SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                    blurMatrix),
-             readImage->width() / static_cast<float>(drawSurface->width()), radius, alpha);
+             radius, alpha, blurEffect);
 }
 
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
-                                    const float inverseScale, const float radius,
-                                    const float alpha) const {
+                                    const float radius, const float alpha,
+                                    const sk_sp<SkRuntimeEffect>& blurEffect) const {
     SkPaint paint;
     if (radius == 0) {
         paint.setShader(std::move(input));
         paint.setAlphaf(alpha);
     } else {
-        SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+        SkRuntimeShaderBuilder blurBuilder(blurEffect);
         blurBuilder.child("child") = std::move(input);
+        if (blurEffect == mLowSampleBlurEffect) {
+            blurBuilder.uniform("in_crossFade") = alpha;
+            blurBuilder.uniform("in_weightedCrossFade") = alpha * 0.2f;
+        }
         blurBuilder.uniform("in_blurOffset") = radius;
-        blurBuilder.uniform("in_crossFade") = alpha;
         paint.setShader(blurBuilder.makeShader(nullptr));
     }
     paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
@@ -163,16 +194,19 @@
                 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                   SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                   blurMatrix);
-        blurInto(surfaces[0], std::move(sourceShader), kInputScale, kWeights[0] * step, 1.0f);
+        blurInto(surfaces[0], std::move(sourceShader), kWeights[0] * step, 1.0f,
+                 mLowSampleBlurEffect);
     }
     // Next the remaining downscale blur passes.
     for (int i = 0; i < filterPasses; i++) {
-        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f);
+        // Blur with the higher sample effect into the smaller buffers, for better visual quality.
+        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f,
+                 i == 0 ? mLowSampleBlurEffect : mHighSampleBlurEffect);
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
         blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), kWeights[4 - i] * step,
-                 std::min(1.0f, filterDepth - i));
+                 std::min(1.0f, filterDepth - i), mLowSampleBlurEffect);
     }
     return surfaces[0]->makeTemporaryImage();
 }
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
index 6f4adbf..5efda35 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
@@ -41,13 +41,14 @@
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
 
 private:
-    sk_sp<SkRuntimeEffect> mBlurEffect;
+    sk_sp<SkRuntimeEffect> mLowSampleBlurEffect;
+    sk_sp<SkRuntimeEffect> mHighSampleBlurEffect;
 
     void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkImage>& readImage,
-                  const float radius, const float alpha) const;
+                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
 
     void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkShader> input,
-                  const float inverseScale, const float radius, const float alpha) const;
+                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp
index 10218bb..f262158 100644
--- a/libs/renderengine/skia/filters/LutShader.cpp
+++ b/libs/renderengine/skia/filters/LutShader.cpp
@@ -182,8 +182,8 @@
      * (R1, G1, B1, 0)
      * ...
      */
-    SkImageInfo info = SkImageInfo::Make(length /* the number of rgba */ * 4, 1,
-                                         kRGBA_F16_SkColorType, kPremul_SkAlphaType);
+    SkImageInfo info = SkImageInfo::Make(length /* the number of rgba */, 1, kRGBA_F16_SkColorType,
+                                         kPremul_SkAlphaType);
     SkBitmap bitmap;
     bitmap.allocPixels(info);
     if (!bitmap.installPixels(info, buffer.data(), info.minRowBytes())) {
diff --git a/libs/shaders/OWNERS b/libs/shaders/OWNERS
index 6d91da3..6977a49 100644
--- a/libs/shaders/OWNERS
+++ b/libs/shaders/OWNERS
@@ -1,4 +1,3 @@
 alecmouri@google.com
 jreck@google.com
 sallyqi@google.com
-scroggo@google.com
\ No newline at end of file
diff --git a/libs/tonemap/OWNERS b/libs/tonemap/OWNERS
index 6d91da3..6977a49 100644
--- a/libs/tonemap/OWNERS
+++ b/libs/tonemap/OWNERS
@@ -1,4 +1,3 @@
 alecmouri@google.com
 jreck@google.com
 sallyqi@google.com
-scroggo@google.com
\ No newline at end of file
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index b7ef9b3..78e84fc 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -444,7 +444,7 @@
             (edid.hashedBlockZeroSerialNumberOpt.value_or(0) >> 11) ^
             (edid.hashedDescriptorBlockSerialNumberOpt.value_or(0) << 23);
 
-    return PhysicalDisplayId::fromEdidHash(id);
+    return PhysicalDisplayId::fromValue(id);
 }
 
 } // namespace android
diff --git a/libs/ui/OWNERS b/libs/ui/OWNERS
index a0b5fe7..2a85a4b 100644
--- a/libs/ui/OWNERS
+++ b/libs/ui/OWNERS
@@ -2,6 +2,5 @@
 alecmouri@google.com
 chrisforbes@google.com
 jreck@google.com
-lpy@google.com
 mathias@google.com
 romainguy@google.com
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index 13ed754..937e3f1 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -30,27 +30,16 @@
     // Flag indicating that the display is virtual.
     static constexpr uint64_t FLAG_VIRTUAL = 1ULL << 63;
 
-    // TODO(b/162612135) Remove default constructor
+    // TODO: b/162612135 - Remove default constructor.
     DisplayId() = default;
     constexpr DisplayId(const DisplayId&) = default;
     DisplayId& operator=(const DisplayId&) = default;
 
+    static constexpr DisplayId fromValue(uint64_t value) { return DisplayId(value); }
     constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; }
 
     uint64_t value;
 
-    // For deserialization.
-    static constexpr std::optional<DisplayId> fromValue(uint64_t);
-
-    // As above, but also upcast to Id.
-    template <typename Id>
-    static constexpr std::optional<Id> fromValue(uint64_t value) {
-        if (const auto id = Id::tryCast(DisplayId(value))) {
-            return id;
-        }
-        return {};
-    }
-
 protected:
     explicit constexpr DisplayId(uint64_t id) : value(id) {}
 };
@@ -74,6 +63,9 @@
 
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
+    // TODO: b/162612135 - Remove default constructor.
+    PhysicalDisplayId() = default;
+
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
         if (id.isVirtual()) {
             return std::nullopt;
@@ -87,11 +79,6 @@
         return PhysicalDisplayId(FLAG_STABLE, port, manufacturerId, modelHash);
     }
 
-    // Returns a stable and consistent ID based exclusively on EDID information.
-    static constexpr PhysicalDisplayId fromEdidHash(uint64_t hashedEdid) {
-        return PhysicalDisplayId(hashedEdid);
-    }
-
     // Returns an unstable ID. If EDID is available using "fromEdid" is preferred.
     static constexpr PhysicalDisplayId fromPort(uint8_t port) {
         constexpr uint16_t kManufacturerId = 0;
@@ -99,8 +86,9 @@
         return PhysicalDisplayId(0, port, kManufacturerId, kModelHash);
     }
 
-    // TODO(b/162612135) Remove default constructor
-    PhysicalDisplayId() = default;
+    static constexpr PhysicalDisplayId fromValue(uint64_t value) {
+        return PhysicalDisplayId(value);
+    }
 
     constexpr uint8_t getPort() const { return static_cast<uint8_t>(value); }
 
@@ -131,8 +119,15 @@
         return std::nullopt;
     }
 
+    static constexpr VirtualDisplayId fromValue(uint64_t value) {
+        return VirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 protected:
+    struct SkipVirtualFlag {};
+    constexpr VirtualDisplayId(SkipVirtualFlag, uint64_t value) : DisplayId(value) {}
     explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {}
+
     explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
@@ -146,8 +141,12 @@
         return std::nullopt;
     }
 
+    static constexpr HalVirtualDisplayId fromValue(uint64_t value) {
+        return HalVirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 private:
-    explicit constexpr HalVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+    using VirtualDisplayId::VirtualDisplayId;
 };
 
 struct GpuVirtualDisplayId : VirtualDisplayId {
@@ -160,8 +159,12 @@
         return std::nullopt;
     }
 
+    static constexpr GpuVirtualDisplayId fromValue(uint64_t value) {
+        return GpuVirtualDisplayId(SkipVirtualFlag{}, value);
+    }
+
 private:
-    explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+    using VirtualDisplayId::VirtualDisplayId;
 };
 
 // HalDisplayId is the ID of a display which is managed by HWC.
@@ -177,20 +180,13 @@
         return HalDisplayId(id);
     }
 
+    static constexpr HalDisplayId fromValue(uint64_t value) { return HalDisplayId(value); }
+
 private:
+    using DisplayId::DisplayId;
     explicit constexpr HalDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
-constexpr std::optional<DisplayId> DisplayId::fromValue(uint64_t value) {
-    if (const auto id = fromValue<PhysicalDisplayId>(value)) {
-        return id;
-    }
-    if (const auto id = fromValue<VirtualDisplayId>(value)) {
-        return id;
-    }
-    return {};
-}
-
 static_assert(sizeof(DisplayId) == sizeof(uint64_t));
 static_assert(sizeof(HalDisplayId) == sizeof(uint64_t));
 static_assert(sizeof(VirtualDisplayId) == sizeof(uint64_t));
diff --git a/libs/ui/include/ui/DisplayMap.h b/libs/ui/include/ui/DisplayMap.h
index 65d2b8f..834a304 100644
--- a/libs/ui/include/ui/DisplayMap.h
+++ b/libs/ui/include/ui/DisplayMap.h
@@ -18,6 +18,7 @@
 
 #include <ftl/small_map.h>
 #include <ftl/small_vector.h>
+#include <ftl/unit.h>
 
 namespace android::ui {
 
@@ -30,6 +31,8 @@
 constexpr size_t kPhysicalDisplayCapacity = 3;
 template <typename Key, typename Value>
 using PhysicalDisplayMap = ftl::SmallMap<Key, Value, kPhysicalDisplayCapacity>;
+template <typename Key>
+using PhysicalDisplaySet = ftl::SmallMap<Key, ftl::Unit, kPhysicalDisplayCapacity>;
 
 template <typename T>
 using DisplayVector = ftl::SmallVector<T, kDisplayCapacity>;
diff --git a/libs/ui/include/ui/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
index 06be6db..c0b83b8 100644
--- a/libs/ui/include/ui/ShadowSettings.h
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -46,9 +46,6 @@
     // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
     float length = 0.f;
 
-    // Length of the cast shadow that is drawn by the client.
-    float clientDrawnLength = 0.f;
-
     // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
     // Otherwise the shadow will only be drawn around the edges of the casting layer.
     bool casterIsTranslucent = false;
@@ -58,7 +55,6 @@
     return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
             lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
             lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
-            lhs.clientDrawnLength == rhs.clientDrawnLength &&
             lhs.casterIsTranslucent == rhs.casterIsTranslucent;
 }
 
diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp
index 090d2ee..209acba 100644
--- a/libs/ui/tests/DisplayId_test.cpp
+++ b/libs/ui/tests/DisplayId_test.cpp
@@ -33,7 +33,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+    EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createPhysicalIdFromPort) {
@@ -47,7 +47,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+    EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createGpuVirtualId) {
@@ -59,7 +59,7 @@
     EXPECT_FALSE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
+    EXPECT_EQ(id, GpuVirtualDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) {
@@ -83,7 +83,7 @@
     EXPECT_TRUE(HalDisplayId::tryCast(id));
 
     EXPECT_EQ(id, DisplayId::fromValue(id.value));
-    EXPECT_EQ(id, DisplayId::fromValue<HalVirtualDisplayId>(id.value));
+    EXPECT_EQ(id, HalVirtualDisplayId::fromValue(id.value));
 }
 
 TEST(DisplayIdTest, createVirtualIdFromHalVirtualId) {
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 5fe9484..0dd9f19 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -134,6 +134,11 @@
 
     if (cnx->egl.eglGetPlatformDisplay) {
         std::vector<EGLAttrib> attrs;
+        // These must have the same lifetime as |attrs|, because |attrs| contains pointers to these
+        // variables.
+        std::vector<const char*> enabled;  // ANGLE features to enable
+        std::vector<const char*> disabled; // ANGLE features to disable
+
         if (attrib_list) {
             for (const EGLAttrib* attr = attrib_list; *attr != EGL_NONE; attr += 2) {
                 attrs.push_back(attr[0]);
@@ -142,9 +147,6 @@
         }
 
         if (graphicsenv_flags::feature_overrides()) {
-            std::vector<const char*> enabled;  // ANGLE features to enable
-            std::vector<const char*> disabled; // ANGLE features to disable
-
             // Get the list of ANGLE features to enable from Global.Settings.
             const auto& eglFeatures = GraphicsEnv::getInstance().getAngleEglFeatures();
             for (const std::string& eglFeature : eglFeatures) {
@@ -154,25 +156,24 @@
             // Get the list of ANGLE features to enable/disable from gpuservice.
             GraphicsEnv::getInstance().getAngleFeatureOverrides(enabled, disabled);
             if (!enabled.empty()) {
-                enabled.push_back(0);
+                enabled.push_back(nullptr);
                 attrs.push_back(EGL_FEATURE_OVERRIDES_ENABLED_ANGLE);
                 attrs.push_back(reinterpret_cast<EGLAttrib>(enabled.data()));
             }
             if (!disabled.empty()) {
-                disabled.push_back(0);
+                disabled.push_back(nullptr);
                 attrs.push_back(EGL_FEATURE_OVERRIDES_DISABLED_ANGLE);
                 attrs.push_back(reinterpret_cast<EGLAttrib>(disabled.data()));
             }
         } else {
             const auto& eglFeatures = GraphicsEnv::getInstance().getAngleEglFeatures();
-            std::vector<const char*> features;
-            if (eglFeatures.size() > 0) {
+            if (!eglFeatures.empty()) {
                 for (const std::string& eglFeature : eglFeatures) {
-                    features.push_back(eglFeature.c_str());
+                    enabled.push_back(eglFeature.c_str());
                 }
-                features.push_back(0);
+                enabled.push_back(nullptr);
                 attrs.push_back(EGL_FEATURE_OVERRIDES_ENABLED_ANGLE);
-                attrs.push_back(reinterpret_cast<EGLAttrib>(features.data()));
+                attrs.push_back(reinterpret_cast<EGLAttrib>(enabled.data()));
             }
         }
 
diff --git a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
index 7c255ed..5ba72af 100644
--- a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
+++ b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
@@ -113,7 +113,7 @@
     if (producer == NULL)
         goto not_valid_surface;
 
-    window = new android::Surface(producer, true);
+    window = android::sp<android::Surface>::make(producer, true);
 
     if (window == NULL)
         goto not_valid_surface;
diff --git a/services/automotive/display/AutomotiveDisplayProxyService.cpp b/services/automotive/display/AutomotiveDisplayProxyService.cpp
index d205231..afa6233 100644
--- a/services/automotive/display/AutomotiveDisplayProxyService.cpp
+++ b/services/automotive/display/AutomotiveDisplayProxyService.cpp
@@ -34,10 +34,8 @@
     sp<IBinder> displayToken = nullptr;
     sp<SurfaceControl> surfaceControl = nullptr;
     if (it == mDisplays.end()) {
-        if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
-            displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
-        }
-
+        displayToken =
+                SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId::fromValue(id));
         if (displayToken == nullptr) {
             ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
             return nullptr;
@@ -160,11 +158,8 @@
     HwDisplayConfig activeConfig;
     HwDisplayState  activeState;
 
-    sp<IBinder> displayToken;
-    if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
-        displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
-    }
-
+    sp<IBinder> displayToken =
+            SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId::fromValue(id));
     if (displayToken == nullptr) {
         ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
     } else {
@@ -197,4 +192,3 @@
 }  // namespace automotive
 }  // namespace frameworks
 }  // namespace android
-
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index 5c7f344..e96b17a 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -2,3 +2,4 @@
     class hal
     user graphics
     group automotive_evs
+    disabled
diff --git a/services/displayservice/DisplayEventReceiver.cpp b/services/displayservice/DisplayEventReceiver.cpp
index 2bb74c2..9927fb6 100644
--- a/services/displayservice/DisplayEventReceiver.cpp
+++ b/services/displayservice/DisplayEventReceiver.cpp
@@ -22,6 +22,7 @@
 #include <android/frameworks/displayservice/1.0/BpHwEventCallback.h>
 
 #include <thread>
+#include <ftl/enum.h>
 
 namespace android {
 namespace frameworks {
@@ -97,11 +98,11 @@
         for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
             const FwkReceiver::Event &event = buf[i];
 
-            uint32_t type = event.header.type;
+            android::DisplayEventType type = event.header.type;
             uint64_t timestamp = event.header.timestamp;
 
             switch(buf[i].header.type) {
-                case FwkReceiver::DISPLAY_EVENT_VSYNC: {
+                case DisplayEventType::DISPLAY_EVENT_VSYNC: {
                     auto ret = mCallback->onVsync(timestamp, event.vsync.count);
                     if (!ret.isOk()) {
                         LOG(ERROR) << "AttachedEvent handleEvent fails on onVsync callback"
@@ -109,7 +110,7 @@
                         return 0;  // remove the callback
                     }
                 } break;
-                case FwkReceiver::DISPLAY_EVENT_HOTPLUG: {
+                case DisplayEventType::DISPLAY_EVENT_HOTPLUG: {
                     auto ret = mCallback->onHotplug(timestamp, event.hotplug.connected);
                     if (!ret.isOk()) {
                         LOG(ERROR) << "AttachedEvent handleEvent fails on onHotplug callback"
@@ -118,7 +119,8 @@
                     }
                 } break;
                 default: {
-                    LOG(ERROR) << "AttachedEvent handleEvent unknown type: " << type;
+                    LOG(ERROR) << "AttachedEvent handleEvent unknown type: "
+                                << ftl::to_underlying(type);
                 }
             }
         }
diff --git a/services/displayservice/OWNERS b/services/displayservice/OWNERS
index 7a3e4c2..40164aa 100644
--- a/services/displayservice/OWNERS
+++ b/services/displayservice/OWNERS
@@ -1,2 +1 @@
 smoreland@google.com
-lpy@google.com
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 689221f..74e354f 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -7,6 +7,13 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "gpuservice_flags",
+    package: "com.android.frameworks.gpuservice.flags",
+    container: "system",
+    srcs: ["gpuservice_flags.aconfig"],
+}
+
 cc_defaults {
     name: "gpuservice_defaults",
     cflags: [
@@ -20,6 +27,11 @@
 }
 
 cc_aconfig_library {
+    name: "gpuservice_multiuser_flags_c_lib",
+    aconfig_declarations: "gpuservice_flags",
+}
+
+cc_aconfig_library {
     name: "gpuservice_flags_c_lib",
     aconfig_declarations: "graphicsenv_flags",
 }
@@ -27,6 +39,7 @@
 cc_defaults {
     name: "libgpuservice_defaults",
     defaults: [
+        "aconfig_lib_cc_static_link.defaults",
         "gpuservice_defaults",
         "libfeatureoverride_deps",
         "libgfxstats_deps",
@@ -92,6 +105,9 @@
     srcs: [
         ":libgpuservice_sources",
     ],
+    shared_libs: [
+        "gpuservice_multiuser_flags_c_lib",
+    ],
 }
 
 cc_defaults {
@@ -126,4 +142,7 @@
     static_libs: [
         "libgpuservice",
     ],
+    shared_libs: [
+        "gpuservice_multiuser_flags_c_lib",
+    ],
 }
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index fadb1fd..62e2d1a 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -24,7 +24,11 @@
 #include <binder/IResultReceiver.h>
 #include <binder/Parcel.h>
 #include <binder/PermissionCache.h>
+#include <com_android_frameworks_gpuservice_flags.h>
+#include <com_android_graphics_graphicsenv_flags.h>
 #include <cutils/properties.h>
+#include <cutils/multiuser.h>
+#include <feature_override/FeatureOverrideParser.h>
 #include <gpumem/GpuMem.h>
 #include <gpuwork/GpuWork.h>
 #include <gpustats/GpuStats.h>
@@ -38,6 +42,9 @@
 #include <thread>
 #include <memory>
 
+namespace gpuservice_flags = com::android::frameworks::gpuservice::flags;
+namespace graphicsenv_flags = com::android::graphics::graphicsenv::flags;
+
 namespace android {
 
 using base::StringAppendF;
@@ -113,11 +120,22 @@
 
     // only system_server with the ACCESS_GPU_SERVICE permission is allowed to set
     // persist.graphics.egl
-    if (uid != AID_SYSTEM ||
-        !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) {
-        ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() "
+    if (gpuservice_flags::multiuser_permission_check()) {
+        // retrieve the appid of Settings app on multiuser builds
+        const int multiuserappid = multiuser_get_app_id(uid);
+        if (multiuserappid != AID_SYSTEM ||
+            !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) {
+            ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() "
+                "pid=%d, uid=%d\n, multiuserappid=%d", pid, uid, multiuserappid);
+            return;
+        }
+    } else {
+        if (uid != AID_SYSTEM ||
+            !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) {
+            ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() "
                 "pid=%d, uid=%d\n", pid, uid);
-        return;
+            return;
+        }
     }
 
     std::lock_guard<std::mutex> lock(mLock);
@@ -128,7 +146,6 @@
     }
 }
 
-
 void GpuService::setUpdatableDriverPath(const std::string& driverPath) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
@@ -156,7 +173,11 @@
     for (size_t i = 0, n = args.size(); i < n; i++)
         ALOGV("  arg[%zu]: '%s'", i, String8(args[i]).c_str());
 
-    if (args.size() >= 1) {
+    if (!args.empty()) {
+        if (graphicsenv_flags::feature_overrides()) {
+            if (args[0] == String16("featureOverrides"))
+                return cmdFeatureOverrides(out, err);
+        }
         if (args[0] == String16("vkjson")) return cmdVkjson(out, err);
         if (args[0] == String16("vkprofiles")) return cmdVkprofiles(out, err);
         if (args[0] == String16("help")) return cmdHelp(out);
@@ -220,6 +241,11 @@
     return NO_ERROR;
 }
 
+status_t GpuService::cmdFeatureOverrides(int out, int /*err*/) {
+    dprintf(out, "%s\n", mFeatureOverrideParser.getFeatureOverrides().toString().c_str());
+    return NO_ERROR;
+}
+
 namespace {
 
 status_t cmdHelp(int out) {
@@ -232,6 +258,10 @@
             "GPU Service commands:\n"
             "  vkjson      dump Vulkan properties as JSON\n"
             "  vkprofiles  print support for select Vulkan profiles\n");
+    if (graphicsenv_flags::feature_overrides()) {
+        fprintf(outs,
+                "  featureOverrides  update and output gpuservice's feature overrides\n");
+    }
     fclose(outs);
     return NO_ERROR;
 }
diff --git a/services/gpuservice/feature_override/Android.bp b/services/gpuservice/feature_override/Android.bp
index cda67f0..3b5407b 100644
--- a/services/gpuservice/feature_override/Android.bp
+++ b/services/gpuservice/feature_override/Android.bp
@@ -34,9 +34,7 @@
         "libbase",
         "libgraphicsenv",
         "liblog",
-    ],
-    static_libs: [
-        "libprotobuf-cpp-lite-ndk",
+        "libprotobuf-cpp-lite",
     ],
 }
 
diff --git a/services/gpuservice/feature_override/FeatureOverrideParser.cpp b/services/gpuservice/feature_override/FeatureOverrideParser.cpp
index 1ad637c..a16bfa8 100644
--- a/services/gpuservice/feature_override/FeatureOverrideParser.cpp
+++ b/services/gpuservice/feature_override/FeatureOverrideParser.cpp
@@ -90,7 +90,6 @@
 }
 
 void FeatureOverrideParser::forceFileRead() {
-    resetFeatureOverrides(mFeatureOverrides);
     mLastProtobufReadTime = 0;
 }
 
@@ -98,6 +97,9 @@
     const feature_override::FeatureOverrideProtos overridesProtos = readFeatureConfigProtos(
             getFeatureOverrideFilePath());
 
+    // Clear out the stale values before adding the newly parsed data.
+    resetFeatureOverrides(mFeatureOverrides);
+
     // Global feature overrides.
     for (const auto &featureConfigProto: overridesProtos.global_features()) {
         FeatureConfig featureConfig;
diff --git a/services/gpuservice/gpuservice_flags.aconfig b/services/gpuservice/gpuservice_flags.aconfig
new file mode 100644
index 0000000..be6a7bb
--- /dev/null
+++ b/services/gpuservice/gpuservice_flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.frameworks.gpuservice.flags"
+container: "system"
+
+flag {
+    name: "multiuser_permission_check"
+    namespace: "gpu"
+    description: "Whether to consider headless system user mode/multiuser when checking toggleAngleAsSystemDriver permission."
+    bug: "389867658"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/gpuservice/include/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h
index 057d127..116b6d7 100644
--- a/services/gpuservice/include/gpuservice/GpuService.h
+++ b/services/gpuservice/include/gpuservice/GpuService.h
@@ -86,6 +86,8 @@
 
     status_t doDump(int fd, const Vector<String16>& args, bool asProto);
 
+    status_t cmdFeatureOverrides(int out, int /*err*/);
+
     /*
      * Attributes
      */
diff --git a/services/gpuservice/tests/fuzzers/Android.bp b/services/gpuservice/tests/fuzzers/Android.bp
index d4d48c4..7be3253 100644
--- a/services/gpuservice/tests/fuzzers/Android.bp
+++ b/services/gpuservice/tests/fuzzers/Android.bp
@@ -13,6 +13,9 @@
         "libgpuservice",
         "liblog",
     ],
+    shared_libs: [
+        "gpuservice_multiuser_flags_c_lib",
+    ],
     fuzz_config: {
         cc: [
             "paulthomson@google.com",
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index d2184d8..0dac24d 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -89,6 +89,7 @@
     ],
     header_libs: ["bpf_headers"],
     shared_libs: [
+        "gpuservice_multiuser_flags_c_lib",
         "libbase",
         "libbinder",
         "libbpf_bcc",
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 3140dc8..d02e643 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -19,6 +19,7 @@
 #include <android-base/logging.h>
 #include <android/configuration.h>
 #include <com_android_input_flags.h>
+#include <algorithm>
 #if defined(__ANDROID__)
 #include <gui/SurfaceComposerClient.h>
 #endif
@@ -165,6 +166,7 @@
         mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
         mShowTouchesEnabled(false),
         mStylusPointerIconEnabled(false),
+        mPointerMotionFilterEnabled(false),
         mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
         mIsWindowInfoListenerRegistered(false),
         mWindowInfoListener(sp<PointerChoreographerDisplayInfoListener>::make(this)),
@@ -322,8 +324,10 @@
                                                                  PointerControllerInterface& pc) {
     const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
     const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-
-    vec2 unconsumedDelta = pc.move(deltaX, deltaY);
+    vec2 filteredDelta =
+            filterPointerMotionForAccessibilityLocked(pc.getPosition(), vec2{deltaX, deltaY},
+                                                      newArgs.displayId);
+    vec2 unconsumedDelta = pc.move(filteredDelta.x, filteredDelta.y);
     if (com::android::input::flags::connected_displays_cursor() &&
         (std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
         handleUnconsumedDeltaLocked(pc, unconsumedDelta);
@@ -638,6 +642,8 @@
                          mShowTouchesEnabled ? "true" : "false");
     dump += StringPrintf(INDENT "Stylus PointerIcon Enabled: %s\n",
                          mStylusPointerIconEnabled ? "true" : "false");
+    dump += StringPrintf(INDENT "Accessibility Pointer Motion Filter Enabled: %s\n",
+                         mPointerMotionFilterEnabled ? "true" : "false");
 
     dump += INDENT "MousePointerControllers:\n";
     for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
@@ -973,6 +979,11 @@
     mCurrentFocusedDisplay = displayId;
 }
 
+void PointerChoreographer::setAccessibilityPointerMotionFilterEnabled(bool enabled) {
+    std::scoped_lock _l(getLock());
+    mPointerMotionFilterEnabled = enabled;
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
@@ -1046,6 +1057,21 @@
     return std::nullopt;
 }
 
+vec2 PointerChoreographer::filterPointerMotionForAccessibilityLocked(
+        const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) {
+    if (!mPointerMotionFilterEnabled) {
+        return delta;
+    }
+    std::optional<vec2> filterResult =
+            mPolicy.filterPointerMotionForAccessibility(current, delta, displayId);
+    if (!filterResult.has_value()) {
+        // Disable filter when there's any error.
+        mPointerMotionFilterEnabled = false;
+        return delta;
+    }
+    return *filterResult;
+}
+
 // --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index a9d971a..2435125 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -90,6 +90,11 @@
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
     virtual void dump(std::string& dump) = 0;
+
+    /**
+     * Enables motion event filter before pointer coordinates are determined.
+     */
+    virtual void setAccessibilityPointerMotionFilterEnabled(bool enabled) = 0;
 };
 
 class PointerChoreographer : public PointerChoreographerInterface {
@@ -110,6 +115,7 @@
     void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
     void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
     void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
+    void setAccessibilityPointerMotionFilterEnabled(bool enabled) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyKey(const NotifyKeyArgs& args) override;
@@ -168,6 +174,10 @@
                                  const DisplayTopologyPosition sourceBoundary,
                                  int32_t sourceCursorOffsetPx) const REQUIRES(getLock());
 
+    vec2 filterPointerMotionForAccessibilityLocked(const vec2& current, const vec2& delta,
+                                                   const ui::LogicalDisplayId& displayId)
+            REQUIRES(getLock());
+
     /* Topology is initialized with default-constructed value, which is an empty topology. Till we
      * receive setDisplayTopology call.
      * Meanwhile Choreographer will treat every display as independent disconnected display.
@@ -228,6 +238,7 @@
     std::vector<DisplayViewport> mViewports GUARDED_BY(getLock());
     bool mShowTouchesEnabled GUARDED_BY(getLock());
     bool mStylusPointerIconEnabled GUARDED_BY(getLock());
+    bool mPointerMotionFilterEnabled GUARDED_BY(getLock());
     std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
     ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(getLock());
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9793800..a2543b1 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -798,20 +798,6 @@
     return !isFromSource(source, AINPUT_SOURCE_MOUSE);
 }
 
-/**
- * Return true if stylus is currently down anywhere on the specified display, and false otherwise.
- */
-bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId,
-                             const std::unordered_map<ui::LogicalDisplayId /*displayId*/,
-                                                      TouchState>& touchStatesByDisplay) {
-    const auto it = touchStatesByDisplay.find(displayId);
-    if (it == touchStatesByDisplay.end()) {
-        return false;
-    }
-    const TouchState& state = it->second;
-    return state.hasActiveStylus();
-}
-
 Result<void> validateWindowInfosUpdate(const gui::WindowInfosUpdate& update) {
     std::unordered_set<int32_t> windowIds;
     for (const WindowInfo& info : update.windowInfos) {
@@ -933,6 +919,13 @@
                         binderToString(info.applicationInfo.token).c_str());
 }
 
+bool isMouseOrTouchpad(uint32_t sources) {
+    // Check if this is a mouse or touchpad, but not a drawing tablet.
+    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -985,6 +978,9 @@
     resetKeyRepeatLocked();
     releasePendingEventLocked();
     drainInboundQueueLocked();
+#if defined(__ANDROID__)
+    SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener);
+#endif
     mCommandQueue.clear();
 }
 
@@ -1155,9 +1151,7 @@
 
     // If dispatching is frozen, do not process timeouts or try to deliver any new events.
     if (mDispatchFrozen) {
-        if (DEBUG_FOCUS) {
-            ALOGD("Dispatch frozen.  Waiting some more.");
-        }
+        LOG_IF(INFO, DEBUG_FOCUS) << "Dispatch frozen.  Waiting some more.";
         return;
     }
 
@@ -1350,9 +1344,8 @@
 
         // Alternatively, maybe there's a spy window that could handle this event.
         const std::vector<sp<WindowInfoHandle>> touchedSpies =
-                mWindowInfos.findTouchedSpyWindowsAt(displayId, x, y, isStylus,
-                                                     motionEntry.deviceId,
-                                                     mTouchStates.mTouchStatesByDisplay);
+                findTouchedSpyWindowsAt(displayId, x, y, isStylus, motionEntry.deviceId,
+                                        mWindowInfos);
         for (const auto& windowHandle : touchedSpies) {
             const std::shared_ptr<Connection> connection =
                     mConnectionManager.getConnection(windowHandle->getToken());
@@ -1498,28 +1491,26 @@
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
             std::bitset<MAX_POINTER_ID + 1> pointerIds;
             pointerIds.set(pointerId);
-            DispatcherTouchState::addPointerWindowTarget(windowHandle,
-                                                         InputTarget::DispatchMode::OUTSIDE,
-                                                         ftl::Flags<InputTarget::Flags>(),
-                                                         pointerIds,
-                                                         /*firstDownTimeInTarget=*/std::nullopt,
-                                                         connections, windowInfos, dump,
-                                                         outsideTargets);
+            addPointerWindowTarget(windowHandle, InputTarget::DispatchMode::OUTSIDE,
+                                   ftl::Flags<InputTarget::Flags>(), pointerIds,
+                                   /*firstDownTimeInTarget=*/std::nullopt,
+                                   /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
+                                   dump, outsideTargets);
         }
     }
     return outsideTargets;
 }
 
-std::vector<sp<WindowInfoHandle>> InputDispatcher::DispatcherWindowInfo::findTouchedSpyWindowsAt(
+std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAt(
         ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
-        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) const {
+        const DispatcherWindowInfo& windowInfos) {
     // Traverse windows from front to back and gather the touched spy windows.
     std::vector<sp<WindowInfoHandle>> spyWindows;
-    const auto& windowHandles = getWindowHandlesForDisplay(displayId);
+    const ui::Transform displayTransform = windowInfos.getDisplayTransform(displayId);
+    const auto& windowHandles = windowInfos.getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         const WindowInfo& info = *windowHandle->getInfo();
-        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus,
-                                  getDisplayTransform(displayId))) {
+        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, displayTransform)) {
             // Skip if the pointer is outside of the window.
             continue;
         }
@@ -1536,9 +1527,7 @@
     const char* reason;
     switch (dropReason) {
         case DropReason::POLICY:
-            if (debugInboundEventDetails()) {
-                ALOGD("Dropped event because policy consumed it.");
-            }
+            LOG_IF(INFO, debugInboundEventDetails()) << "Dropped event because policy consumed it.";
             reason = "inbound event was dropped because the policy consumed it";
             break;
         case DropReason::DISABLED:
@@ -1652,9 +1641,7 @@
 void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<const EventEntry> entry) {
     const std::shared_ptr<InjectionState>& injectionState = entry->injectionState;
     if (injectionState && injectionState->injectionResult == InputEventInjectionResult::PENDING) {
-        if (DEBUG_DISPATCH_CYCLE) {
-            ALOGD("Injected inbound event was dropped.");
-        }
+        LOG_IF(INFO, DEBUG_DISPATCH_CYCLE) << "Injected inbound event was dropped.";
         setInjectionResult(*entry, InputEventInjectionResult::FAILED);
     }
     if (entry == mNextUnblockedEvent) {
@@ -1694,10 +1681,9 @@
 
 bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime,
                                                 const DeviceResetEntry& entry) {
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("dispatchDeviceReset - eventTime=%" PRId64 ", deviceId=%d", entry.eventTime,
-              entry.deviceId);
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << "dispatchDeviceReset - eventTime=" << entry.eventTime
+            << ", deviceId=" << entry.deviceId;
 
     // Reset key repeating in case a keyboard device was disabled or enabled.
     if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->deviceId == entry.deviceId) {
@@ -1889,9 +1875,8 @@
         } else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry &&
                    mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) {
             // The key on device 'deviceId' is still down, do not stop key repeat
-            if (debugInboundEventDetails()) {
-                ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId);
-            }
+            LOG_IF(INFO, debugInboundEventDetails())
+                    << "deviceId=" << entry->deviceId << " got KEY_UP as stale";
         } else if (!entry->syntheticRepeat) {
             resetKeyRepeatLocked();
         }
@@ -1988,25 +1973,24 @@
 }
 
 void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& entry) {
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%s, "
-              "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, "
-              "metaState=0x%x, repeatCount=%d, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source,
-              entry.displayId.toString().c_str(), entry.policyFlags, entry.action, entry.flags,
-              entry.keyCode, entry.scanCode, entry.metaState, entry.repeatCount, entry.downTime);
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << prefix << "eventTime=" << entry.eventTime << ", deviceId=" << entry.deviceId
+            << ", source=0x" << std::hex << entry.source
+            << ", displayId=" << entry.displayId.toString() << ", policyFlags=0x"
+            << entry.policyFlags << ", action=0x" << entry.action << ", flags=0x" << entry.flags
+            << ", keyCode=0x" << entry.keyCode << ", scanCode=0x" << entry.scanCode
+            << ", metaState=0x" << entry.metaState << ", repeatCount=" << std::dec
+            << entry.repeatCount << ", downTime=" << entry.downTime;
 }
 
 void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
                                            const std::shared_ptr<const SensorEntry>& entry,
                                            DropReason* dropReason, nsecs_t& nextWakeupTime) {
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
-              "source=0x%x, sensorType=%s",
-              entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
-              ftl::enum_string(entry->sensorType).c_str());
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << "notifySensorEvent eventTime=" << entry->eventTime
+            << ", hwTimestamp=" << entry->hwTimestamp << ", deviceId=" << entry->deviceId
+            << ", source=0x" << std::hex << entry->source << std::dec
+            << ", sensorType=" << ftl::enum_string(entry->sensorType);
     auto command = [this, entry]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
 
@@ -2020,10 +2004,9 @@
 }
 
 bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType) {
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("flushSensor deviceId=%d, sensorType=%s", deviceId,
-              ftl::enum_string(sensorType).c_str());
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << "flushSensor deviceId=" << deviceId
+            << ", sensorType=" << ftl::enum_string(sensorType).c_str();
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -2193,9 +2176,7 @@
                                           std::shared_ptr<const EventEntry> eventEntry,
                                           const std::vector<InputTarget>& inputTargets) {
     ATRACE_CALL();
-    if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("dispatchEventToCurrentInputTargets");
-    }
+    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE) << "dispatchEventToCurrentInputTargets";
 
     processInteractionsLocked(*eventEntry, inputTargets);
 
@@ -2238,9 +2219,7 @@
 }
 
 void InputDispatcher::resetNoFocusedWindowTimeoutLocked() {
-    if (DEBUG_FOCUS) {
-        ALOGD("Resetting ANR timeouts.");
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "Resetting ANR timeouts.";
 
     // Reset input target wait timeout.
     mNoFocusedWindowTimeoutTime = std::nullopt;
@@ -2413,12 +2392,11 @@
     const int32_t maskedAction = MotionEvent::getActionMasked(action);
 
     // Copy current touch state into tempTouchState.
-    // This state will be used to update mTouchStatesByDisplay at the end of this function.
+    // This state will be used to update saved touch state at the end of this function.
     // If no state for the specified display exists, then our initial state will be empty.
-    const TouchState* oldState = nullptr;
+    const TouchState* oldState = getTouchStateForMotionEntry(entry, windowInfos);
     TouchState tempTouchState;
-    if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
-        oldState = &(it->second);
+    if (oldState != nullptr) {
         tempTouchState = *oldState;
     }
 
@@ -2480,8 +2458,7 @@
         }
 
         std::vector<sp<WindowInfoHandle>> newTouchedWindows =
-                windowInfos.findTouchedSpyWindowsAt(displayId, x, y, isStylus, entry.deviceId,
-                                                    mTouchStatesByDisplay);
+                findTouchedSpyWindowsAt(displayId, x, y, isStylus, entry.deviceId, windowInfos);
         if (newTouchedWindowHandle != nullptr) {
             // Process the foreground window first so that it is the first to receive the event.
             newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
@@ -2494,8 +2471,7 @@
         }
 
         for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
-            if (!canWindowReceiveMotion(windowHandle, entry, connections, windowInfos,
-                                        mTouchStatesByDisplay)) {
+            if (!canWindowReceiveMotion(windowHandle, entry, connections, windowInfos)) {
                 continue;
             }
 
@@ -2576,11 +2552,10 @@
         // If the pointer is not currently down, then ignore the event.
         if (!tempTouchState.isDown(entry.deviceId) &&
             maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-            if (DEBUG_DROPPED_EVENTS_VERBOSE) {
-                LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
-                          << " is not down or we previously dropped the pointer down event in "
-                          << "display " << displayId << ": " << entry.getDescription();
-            }
+            LOG_IF(INFO, DEBUG_DROPPED_EVENTS_VERBOSE)
+                    << "Dropping event because the pointer for device " << entry.deviceId
+                    << " is not down or we previously dropped the pointer down event in display "
+                    << displayId << ": " << entry.getDescription();
             return injectionError(InputEventInjectionResult::FAILED);
         }
 
@@ -2618,8 +2593,7 @@
 
             // Do not slide events to the window which can not receive motion event
             if (newTouchedWindowHandle != nullptr &&
-                !canWindowReceiveMotion(newTouchedWindowHandle, entry, connections, windowInfos,
-                                        mTouchStatesByDisplay)) {
+                !canWindowReceiveMotion(newTouchedWindowHandle, entry, connections, windowInfos)) {
                 newTouchedWindowHandle = nullptr;
             }
 
@@ -2636,12 +2610,12 @@
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
-                DispatcherTouchState::
-                        addPointerWindowTarget(oldTouchedWindowHandle,
-                                               InputTarget::DispatchMode::SLIPPERY_EXIT,
-                                               ftl::Flags<InputTarget::Flags>(), pointerIds,
-                                               touchedWindow.getDownTimeInTarget(entry.deviceId),
-                                               connections, windowInfos, dump, targets);
+                addPointerWindowTarget(oldTouchedWindowHandle,
+                                       InputTarget::DispatchMode::SLIPPERY_EXIT,
+                                       ftl::Flags<InputTarget::Flags>(), pointerIds,
+                                       touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                       /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
+                                       dump, targets);
 
                 // Make a slippery entrance into the new window.
 
@@ -2690,12 +2664,11 @@
         std::bitset<MAX_POINTER_ID + 1> pointerIds;
         pointerIds.set(entry.pointerProperties[0].id);
         for (const TouchedWindow& touchedWindow : hoveringWindows) {
-            DispatcherTouchState::addPointerWindowTarget(touchedWindow.windowHandle,
-                                                         touchedWindow.dispatchMode,
-                                                         touchedWindow.targetFlags, pointerIds,
-                                                         touchedWindow.getDownTimeInTarget(
-                                                                 entry.deviceId),
-                                                         connections, windowInfos, dump, targets);
+            addPointerWindowTarget(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                   touchedWindow.targetFlags, pointerIds,
+                                   touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                   /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
+                                   dump, targets);
         }
     }
 
@@ -2748,13 +2721,11 @@
         if (touchingPointers.empty()) {
             continue;
         }
-        DispatcherTouchState::addPointerWindowTarget(touchedWindow.windowHandle,
-                                                     touchedWindow.dispatchMode,
-                                                     touchedWindow.targetFlags,
-                                                     getPointerIds(touchingPointers),
-                                                     touchedWindow.getDownTimeInTarget(
-                                                             entry.deviceId),
-                                                     connections, windowInfos, dump, targets);
+        addPointerWindowTarget(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                               touchedWindow.targetFlags, getPointerIds(touchingPointers),
+                               touchedWindow.getDownTimeInTarget(entry.deviceId),
+                               /*pointerDisplayId=*/displayId, connections, windowInfos, dump,
+                               targets);
     }
 
     // During targeted injection, only allow owned targets to receive events
@@ -2809,16 +2780,12 @@
     if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
         if (displayId >= ui::LogicalDisplayId::DEFAULT) {
             tempTouchState.clearWindowsWithoutPointers();
-            mTouchStatesByDisplay[displayId] = tempTouchState;
+            saveTouchStateForMotionEntry(entry, std::move(tempTouchState), windowInfos);
         } else {
-            mTouchStatesByDisplay.erase(displayId);
+            eraseTouchStateForMotionEntry(entry, windowInfos);
         }
     }
 
-    if (tempTouchState.windows.empty()) {
-        mTouchStatesByDisplay.erase(displayId);
-    }
-
     return targets;
 }
 
@@ -2961,8 +2928,9 @@
         const sp<android::gui::WindowInfoHandle>& windowHandle,
         InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
         std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget,
-        const ConnectionManager& connections, const DispatcherWindowInfo& windowInfos,
-        std::function<void()> dump, std::vector<InputTarget>& inputTargets) {
+        std::optional<ui::LogicalDisplayId> pointerDisplayId, const ConnectionManager& connections,
+        const DispatcherWindowInfo& windowInfos, std::function<void()> dump,
+        std::vector<InputTarget>& inputTargets) {
     if (pointerIds.none()) {
         for (const auto& target : inputTargets) {
             LOG(INFO) << "Target: " << target;
@@ -2997,7 +2965,8 @@
         }
         inputTargets.push_back(
                 createInputTarget(connection, windowHandle, dispatchMode, targetFlags,
-                                  windowInfos.getRawTransform(*windowHandle->getInfo()),
+                                  windowInfos.getRawTransform(*windowHandle->getInfo(),
+                                                              pointerDisplayId),
                                   firstDownTimeInTarget));
         it = inputTargets.end() - 1;
     }
@@ -3261,10 +3230,9 @@
                 return;
             }
             if (windowDisablingUserActivityInfo != nullptr) {
-                if (DEBUG_DISPATCH_CYCLE) {
-                    ALOGD("Not poking user activity: disabled by window '%s'.",
-                          windowDisablingUserActivityInfo->name.c_str());
-                }
+                LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+                        << "Not poking user activity: disabled by window '"
+                        << windowDisablingUserActivityInfo->name << "'.";
                 return;
             }
             break;
@@ -3278,10 +3246,9 @@
             // the apps, like system shortcuts
             if (windowDisablingUserActivityInfo != nullptr &&
                 keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP) {
-                if (DEBUG_DISPATCH_CYCLE) {
-                    ALOGD("Not poking user activity: disabled by window '%s'.",
-                          windowDisablingUserActivityInfo->name.c_str());
-                }
+                LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+                        << "Not poking user activity: disabled by window '"
+                        << windowDisablingUserActivityInfo->name << "'.";
                 return;
             }
             break;
@@ -3309,22 +3276,19 @@
     ATRACE_NAME_IF(ATRACE_ENABLED(),
                    StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
                                 connection->getInputChannelName().c_str(), eventEntry->id));
-    if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
-              "globalScaleFactor=%f, pointerIds=%s %s",
-              connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
-              inputTarget.globalScaleFactor, bitsetToString(inputTarget.getPointerIds()).c_str(),
-              inputTarget.getPointerInfoString().c_str());
-    }
+    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+            << "channel '" << connection->getInputChannelName()
+            << "' ~ prepareDispatchCycle - flags=" << inputTarget.flags.string()
+            << ", globalScaleFactor=" << inputTarget.globalScaleFactor
+            << ", pointerIds=" << bitsetToString(inputTarget.getPointerIds()) << " "
+            << inputTarget.getPointerInfoString();
 
     // Skip this event if the connection status is not normal.
     // We don't want to enqueue additional outbound events if the connection is broken.
     if (connection->status != Connection::Status::NORMAL) {
-        if (DEBUG_DISPATCH_CYCLE) {
-            ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
-                  connection->getInputChannelName().c_str(),
-                  ftl::enum_string(connection->status).c_str());
-        }
+        LOG_IF(INFO, DEBUG_DISPATCH_CYCLE) << "channel '" << connection->getInputChannelName()
+                                           << "' ~ Dropping event because the channel status is "
+                                           << ftl::enum_string(connection->status);
         return;
     }
 
@@ -3443,11 +3407,10 @@
                 if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
                     !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
                                                        motionEntry.displayId)) {
-                    if (DEBUG_DISPATCH_CYCLE) {
-                        LOG(DEBUG) << "channel '" << connection->getInputChannelName().c_str()
-                                   << "' ~ enqueueDispatchEntryLocked: filling in missing hover "
-                                      "enter event";
-                    }
+                    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+                            << "channel '" << connection->getInputChannelName().c_str()
+                            << "' ~ enqueueDispatchEntryLocked: filling in missing hover enter "
+                               "event";
                     resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
                 }
 
@@ -3741,9 +3704,8 @@
     ATRACE_NAME_IF(ATRACE_ENABLED(),
                    StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
                                 connection->getInputChannelName().c_str()));
-    if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
-    }
+    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+            << "channel '" << connection->getInputChannelName() << "' ~ startDispatchCycle";
 
     while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
         std::unique_ptr<DispatchEntry>& dispatchEntry = connection->outboundQueue.front();
@@ -3758,10 +3720,9 @@
             case EventEntry::Type::KEY: {
                 const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
                 std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
-                if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                    LOG(INFO) << "Publishing " << *dispatchEntry << " to "
-                              << connection->getInputChannelName();
-                }
+                LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                        << "Publishing " << *dispatchEntry << " to "
+                        << connection->getInputChannelName();
 
                 // Publish the key event.
                 status = connection->inputPublisher
@@ -3780,10 +3741,9 @@
             }
 
             case EventEntry::Type::MOTION: {
-                if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                    LOG(INFO) << "Publishing " << *dispatchEntry << " to "
-                              << connection->getInputChannelName();
-                }
+                LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                        << "Publishing " << *dispatchEntry << " to "
+                        << connection->getInputChannelName();
                 const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
                 status = publishMotionEvent(*connection, *dispatchEntry);
                 if (status == BAD_VALUE) {
@@ -3856,11 +3816,10 @@
                 } else {
                     // Pipe is full and we are waiting for the app to finish process some events
                     // before sending more events to it.
-                    if (DEBUG_DISPATCH_CYCLE) {
-                        ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
-                              "waiting for the application to catch up",
-                              connection->getInputChannelName().c_str());
-                    }
+                    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+                            << "channel '" << connection->getInputChannelName()
+                            << "' ~ Could not publish event because the pipe is full, waiting for "
+                               "the application to catch up";
                 }
             } else {
                 ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
@@ -3926,10 +3885,9 @@
 void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                 const std::shared_ptr<Connection>& connection,
                                                 uint32_t seq, bool handled, nsecs_t consumeTime) {
-    if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
-              connection->getInputChannelName().c_str(), seq, toString(handled));
-    }
+    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE)
+            << "channel '" << connection->getInputChannelName()
+            << "' ~ finishDispatchCycle - seq=" << seq << ", handled=" << toString(handled);
 
     if (connection->status != Connection::Status::NORMAL) {
         return;
@@ -3944,10 +3902,8 @@
 
 void InputDispatcher::abortBrokenDispatchCycleLocked(const std::shared_ptr<Connection>& connection,
                                                      bool notify) {
-    if (DEBUG_DISPATCH_CYCLE) {
-        LOG(INFO) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
-                  << " - notify=" << toString(notify);
-    }
+    LOG_IF(INFO, DEBUG_DISPATCH_CYCLE) << "channel '" << connection->getInputChannelName() << "'~ "
+                                       << __func__ << " - notify=" << toString(notify);
 
     // Clear the dispatch queues.
     drainDispatchQueue(connection->outboundQueue);
@@ -4072,13 +4028,17 @@
     // Generate cancellations for touched windows first. This is to avoid generating cancellations
     // through a non-touched window if there are more than one window for an input channel.
     if (cancelPointers) {
-        for (const auto& [displayId, touchState] : mTouchStates.mTouchStatesByDisplay) {
-            if (options.displayId.has_value() && options.displayId != displayId) {
-                continue;
-            }
-            for (const auto& touchedWindow : touchState.windows) {
-                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
-            }
+        if (options.displayId.has_value()) {
+            mTouchStates.forAllTouchedWindowsOnDisplay(
+                    options.displayId.value(), [&](const sp<gui::WindowInfoHandle>& windowHandle) {
+                        base::ScopedLockAssertion assumeLocked(mLock);
+                        synthesizeCancelationEventsForWindowLocked(windowHandle, options);
+                    });
+        } else {
+            mTouchStates.forAllTouchedWindows([&](const sp<gui::WindowInfoHandle>& windowHandle) {
+                base::ScopedLockAssertion assumeLocked(mLock);
+                synthesizeCancelationEventsForWindowLocked(windowHandle, options);
+            });
         }
     }
     // Follow up by generating cancellations for all windows, because we don't explicitly track
@@ -4149,12 +4109,11 @@
         return;
     }
 
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
-              "with reality: %s, mode=%s.",
-              connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
-              ftl::enum_string(options.mode).c_str());
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << "channel '" << connection->getInputChannelName() << "' ~ Synthesized "
+            << cancelationEvents.size()
+            << " cancelation events to bring channel back in sync with reality: " << options.reason
+            << ", mode=" << ftl::enum_string(options.mode) << ".";
 
     std::string reason = std::string("reason=").append(options.reason);
     android_log_event_list(LOGTAG_INPUT_CANCEL)
@@ -4210,8 +4169,9 @@
                     DispatcherTouchState::
                             addPointerWindowTarget(window, InputTarget::DispatchMode::AS_IS,
                                                    ftl::Flags<InputTarget::Flags>(), pointerIds,
-                                                   motionEntry.downTime, mConnectionManager,
-                                                   mWindowInfos,
+                                                   motionEntry.downTime,
+                                                   /*pointerDisplayId=*/std::nullopt,
+                                                   mConnectionManager, mWindowInfos,
                                                    std::bind_front(&InputDispatcher::
                                                                            logDispatchStateLocked,
                                                                    this),
@@ -4271,18 +4231,12 @@
         return;
     }
 
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("channel '%s' ~ Synthesized %zu down events to ensure consistent event stream.",
-              connection->getInputChannelName().c_str(), downEvents.size());
-    }
+    LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+            << "channel '" << connection->getInputChannelName() << "' ~ Synthesized "
+            << downEvents.size() << " down events to ensure consistent event stream.";
 
-    const auto [_, touchedWindowState, displayId] =
-            findTouchStateWindowAndDisplay(connection->getToken(),
-                                           mTouchStates.mTouchStatesByDisplay);
-    if (touchedWindowState == nullptr) {
-        LOG(FATAL) << __func__ << ": Touch state is out of sync: No touched window for token";
-    }
-    const auto& windowHandle = touchedWindowState->windowHandle;
+    const auto [windowHandle, displayId] =
+            mTouchStates.findExistingTouchedWindowHandleAndDisplay(connection->getToken());
 
     const bool wasEmpty = connection->outboundQueue.empty();
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
@@ -4303,6 +4257,7 @@
                     DispatcherTouchState::
                             addPointerWindowTarget(windowHandle, InputTarget::DispatchMode::AS_IS,
                                                    targetFlags, pointerIds, motionEntry.downTime,
+                                                   /*pointerDisplayId=*/std::nullopt,
                                                    mConnectionManager, mWindowInfos,
                                                    std::bind_front(&InputDispatcher::
                                                                            logDispatchStateLocked,
@@ -4414,15 +4369,15 @@
 }
 
 void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
-    ALOGD_IF(debugInboundEventDetails(),
-             "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
-             ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, action=%s, flags=0x%x, "
-             "keyCode=%s, scanCode=0x%x, metaState=0x%x, "
-             "downTime=%" PRId64,
-             args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
-             args.displayId.toString().c_str(), args.policyFlags,
-             KeyEvent::actionToString(args.action), args.flags, KeyEvent::getLabel(args.keyCode),
-             args.scanCode, args.metaState, args.downTime);
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifyKey - id=" << args.id << ", eventTime=" << args.eventTime
+            << ", deviceId=" << args.deviceId
+            << ", source=" << inputEventSourceToString(args.source)
+            << ", displayId=" << args.displayId.toString() << ", policyFlags=0x" << std::hex
+            << args.policyFlags << ", action=" << KeyEvent::actionToString(args.action)
+            << ", flags=0x" << args.flags << ", keyCode=" << KeyEvent::getLabel(args.keyCode)
+            << ", scanCode=0x" << args.scanCode << ", metaState=0x" << args.metaState
+            << ", downTime=" << std::dec << args.downTime;
     Result<void> keyCheck = validateKeyEvent(args.action);
     if (!keyCheck.ok()) {
         LOG(ERROR) << "invalid key event: " << keyCheck.error();
@@ -4632,12 +4587,10 @@
 }
 
 void InputDispatcher::notifySensor(const NotifySensorArgs& args) {
-    if (debugInboundEventDetails()) {
-        ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
-              " sensorType=%s",
-              args.id, args.eventTime, args.deviceId, args.source,
-              ftl::enum_string(args.sensorType).c_str());
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifySensor - id=" << args.id << " eventTime=" << args.eventTime
+            << ", deviceId=" << args.deviceId << ", source=0x" << std::hex << args.source
+            << std::dec << ", sensorType=" << ftl::enum_string(args.sensorType);
 
     bool needWake = false;
     { // acquire lock
@@ -4659,10 +4612,9 @@
 }
 
 void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs& args) {
-    if (debugInboundEventDetails()) {
-        ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args.eventTime,
-              args.deviceId, args.isOn);
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifyVibratorState - eventTime=" << args.eventTime << ", device=" << args.deviceId
+            << ", isOn=" << args.isOn;
     mPolicy.notifyVibratorState(args.deviceId, args.isOn);
 }
 
@@ -4671,11 +4623,10 @@
 }
 
 void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) {
-    if (debugInboundEventDetails()) {
-        ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
-              "switchMask=0x%08x",
-              args.eventTime, args.policyFlags, args.switchValues, args.switchMask);
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifySwitch - eventTime=" << args.eventTime << ", policyFlags=0x" << std::hex
+            << args.policyFlags << ", switchValues=0x" << std::setfill('0') << std::setw(8)
+            << args.switchValues << ", switchMask=0x" << std::setw(8) << args.switchMask;
 
     uint32_t policyFlags = args.policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
@@ -4684,10 +4635,8 @@
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     // TODO(b/308677868) Remove device reset from the InputListener interface
-    if (debugInboundEventDetails()) {
-        ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
-              args.deviceId);
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifyDeviceReset - eventTime=" << args.eventTime << ", deviceId=" << args.deviceId;
 
     bool needWake = false;
     { // acquire lock
@@ -4708,10 +4657,9 @@
 }
 
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
-    if (debugInboundEventDetails()) {
-        ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
-              args.request.isEnable() ? "true" : "false");
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << "notifyPointerCaptureChanged - eventTime=%" << args.eventTime
+            << ", enabled=" << toString(args.request.isEnable());
 
     bool needWake = false;
     { // acquire lock
@@ -4771,12 +4719,10 @@
         return InputEventInjectionResult::FAILED;
     }
 
-    if (debugInboundEventDetails()) {
-        LOG(INFO) << __func__ << ": targetUid=" << toString(targetUid, &uidString)
-                  << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
-                  << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec
-                  << ", event=" << *event;
-    }
+    LOG_IF(INFO, debugInboundEventDetails())
+            << __func__ << ": targetUid=" << toString(targetUid, &uidString)
+            << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
+            << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec << ", event=" << *event;
     nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
 
     policyFlags |= POLICY_FLAG_INJECTED | POLICY_FLAG_TRUSTED;
@@ -4953,9 +4899,7 @@
 
     bool needWake = false;
     while (!injectedEntries.empty()) {
-        if (DEBUG_INJECTION) {
-            LOG(INFO) << "Injecting " << injectedEntries.front()->getDescription();
-        }
+        LOG_IF(INFO, DEBUG_INJECTION) << "Injecting " << injectedEntries.front()->getDescription();
         needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front()));
         injectedEntries.pop();
     }
@@ -4981,10 +4925,8 @@
 
                 nsecs_t remainingTimeout = endTime - now();
                 if (remainingTimeout <= 0) {
-                    if (DEBUG_INJECTION) {
-                        ALOGD("injectInputEvent - Timed out waiting for injection result "
-                              "to become available.");
-                    }
+                    LOG_IF(INFO, DEBUG_INJECTION) << "injectInputEvent - Timed out waiting for "
+                                                     "injection result to become available.";
                     injectionResult = InputEventInjectionResult::TIMED_OUT;
                     break;
                 }
@@ -4995,16 +4937,14 @@
             if (injectionResult == InputEventInjectionResult::SUCCEEDED &&
                 syncMode == InputEventInjectionSync::WAIT_FOR_FINISHED) {
                 while (injectionState->pendingForegroundDispatches != 0) {
-                    if (DEBUG_INJECTION) {
-                        ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
-                              injectionState->pendingForegroundDispatches);
-                    }
+                    LOG_IF(INFO, DEBUG_INJECTION) << "injectInputEvent - Waiting for "
+                                                  << injectionState->pendingForegroundDispatches
+                                                  << " pending foreground dispatches.";
                     nsecs_t remainingTimeout = endTime - now();
                     if (remainingTimeout <= 0) {
-                        if (DEBUG_INJECTION) {
-                            ALOGD("injectInputEvent - Timed out waiting for pending foreground "
-                                  "dispatches to finish.");
-                        }
+                        LOG_IF(INFO, DEBUG_INJECTION)
+                                << "injectInputEvent - Timed out waiting for pending foreground "
+                                   "dispatches to finish.";
                         injectionResult = InputEventInjectionResult::TIMED_OUT;
                         break;
                     }
@@ -5015,10 +4955,8 @@
         }
     } // release lock
 
-    if (DEBUG_INJECTION) {
-        LOG(INFO) << "injectInputEvent - Finished with result "
-                  << ftl::enum_string(injectionResult);
-    }
+    LOG_IF(INFO, DEBUG_INJECTION) << "injectInputEvent - Finished with result "
+                                  << ftl::enum_string(injectionResult);
 
     return injectionResult;
 }
@@ -5064,10 +5002,8 @@
     }
 
     InjectionState& injectionState = *entry.injectionState;
-    if (DEBUG_INJECTION) {
-        LOG(INFO) << "Setting input event injection result to "
-                  << ftl::enum_string(injectionResult);
-    }
+    LOG_IF(INFO, DEBUG_INJECTION) << "Setting input event injection result to "
+                                  << ftl::enum_string(injectionResult);
 
     if (injectionState.injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
         // Log the outcome since the injector did not wait for the injection result.
@@ -5234,13 +5170,32 @@
 }
 
 ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform(
-        const android::gui::WindowInfo& windowInfo) const {
+        const android::gui::WindowInfo& windowInfo,
+        std::optional<ui::LogicalDisplayId> pointerDisplayId) const {
+    // TODO(b/383092013): Handle TOPOLOGY_AWARE window flag.
+    // For now, we assume all windows are topology-aware and can handle cross-display streams.
+    if (com::android::input::flags::connected_displays_cursor() && pointerDisplayId.has_value() &&
+        *pointerDisplayId != windowInfo.displayId) {
+        // Sending pointer to a different display than the window. This is a
+        // cross-display drag gesture, so always use the new display's transform.
+        return getDisplayTransform(*pointerDisplayId);
+    }
     // If the window has a cloneLayerStackTransform, always use it as the transform for the "getRaw"
-    // APIs. If not, fall back to using the DisplayInfo transform of the window's display.
-    return (input_flags::use_cloned_screen_coordinates_as_raw() &&
-            windowInfo.cloneLayerStackTransform)
-            ? *windowInfo.cloneLayerStackTransform
-            : getDisplayTransform(windowInfo.displayId);
+    // APIs. If not, fall back to using the DisplayInfo transform of the window's display
+    bool useClonedScreenCoordinates = (input_flags::use_cloned_screen_coordinates_as_raw() &&
+                                       windowInfo.cloneLayerStackTransform);
+    if (useClonedScreenCoordinates) {
+        return *windowInfo.cloneLayerStackTransform;
+    }
+    return getDisplayTransform(windowInfo.displayId);
+}
+
+ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId(
+        ui::LogicalDisplayId displayId) const {
+    if (mTopology.graph.contains(displayId)) {
+        return mTopology.primaryDisplayId;
+    }
+    return displayId;
 }
 
 std::string InputDispatcher::DispatcherWindowInfo::dumpDisplayAndWindowInfo() const {
@@ -5273,11 +5228,10 @@
     return dump;
 }
 
-bool InputDispatcher::canWindowReceiveMotion(
+bool InputDispatcher::DispatcherTouchState::canWindowReceiveMotion(
         const sp<android::gui::WindowInfoHandle>& window,
         const android::inputdispatcher::MotionEntry& motionEntry,
-        const ConnectionManager& connections, const DispatcherWindowInfo& windowInfos,
-        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStates) {
+        const ConnectionManager& connections, const DispatcherWindowInfo& windowInfos) const {
     const WindowInfo& info = *window->getInfo();
 
     // Skip spy window targets that are not valid for targeted injection.
@@ -5331,7 +5285,7 @@
 
     // Ignore touches if stylus is down anywhere on screen
     if (info.inputConfig.test(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH) &&
-        isStylusActiveInDisplay(info.displayId, touchStates)) {
+        isStylusActiveInDisplay(info.displayId)) {
         LOG(INFO) << "Dropping touch from " << window->getName() << " because stylus is active";
         return false;
     }
@@ -5495,9 +5449,7 @@
     // which might not happen until the next GC.
     for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
         if (!mWindowInfos.isWindowPresent(oldWindowHandle)) {
-            if (DEBUG_FOCUS) {
-                ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
-            }
+            LOG_IF(INFO, DEBUG_FOCUS) << "Window went away: " << oldWindowHandle->getName();
             oldWindowHandle->releaseChannel();
         }
     }
@@ -5507,12 +5459,13 @@
 InputDispatcher::DispatcherTouchState::updateFromWindowInfo(
         ui::LogicalDisplayId displayId, const DispatcherWindowInfo& windowInfos) {
     std::list<CancellationArgs> cancellations;
-    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
-        TouchState& state = it->second;
-        cancellations = eraseRemovedWindowsFromWindowInfo(state, displayId, windowInfos);
+    forTouchAndCursorStatesOnDisplay(displayId, [&](TouchState& state) {
+        cancellations.splice(cancellations.end(),
+                             eraseRemovedWindowsFromWindowInfo(state, displayId, windowInfos));
         cancellations.splice(cancellations.end(),
                              updateHoveringStateFromWindowInfo(state, displayId, windowInfos));
-    }
+        return false;
+    });
     return cancellations;
 }
 
@@ -5576,10 +5529,9 @@
 void InputDispatcher::setFocusedApplication(
         ui::LogicalDisplayId displayId,
         const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
-    if (DEBUG_FOCUS) {
-        ALOGD("setFocusedApplication displayId=%s %s", displayId.toString().c_str(),
-              inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "setFocusedApplication displayId=" << displayId.toString() << " "
+                              << (inputApplicationHandle ? inputApplicationHandle->getName()
+                                                         : "<nullptr>");
     { // acquire lock
         std::scoped_lock _l(mLock);
         setFocusedApplicationLocked(displayId, inputApplicationHandle);
@@ -5629,9 +5581,7 @@
  * display. The display-specified events won't be affected.
  */
 void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) {
-    if (DEBUG_FOCUS) {
-        ALOGD("setFocusedDisplay displayId=%s", displayId.toString().c_str());
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "setFocusedDisplay displayId=" << displayId.toString();
     { // acquire lock
         std::scoped_lock _l(mLock);
         ScopedSyntheticEventTracer traceContext(mTracer);
@@ -5685,9 +5635,8 @@
 }
 
 void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
-    if (DEBUG_FOCUS) {
-        ALOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "setInputDispatchMode: enabled=" << enabled
+                              << ", frozen=" << frozen;
 
     bool changed;
     { // acquire lock
@@ -5717,9 +5666,7 @@
 }
 
 void InputDispatcher::setInputFilterEnabled(bool enabled) {
-    if (DEBUG_FOCUS) {
-        ALOGD("setInputFilterEnabled: enabled=%d", enabled);
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "setInputFilterEnabled: enabled=" << enabled;
 
     { // acquire lock
         std::scoped_lock _l(mLock);
@@ -5741,14 +5688,16 @@
     bool needWake = false;
     {
         std::scoped_lock lock(mLock);
-        ALOGD_IF(DEBUG_TOUCH_MODE,
-                 "Request to change touch mode to %s (calling pid=%s, uid=%s, "
-                 "hasPermission=%s, target displayId=%s, mTouchModePerDisplay[displayId]=%s)",
-                 toString(inTouchMode), pid.toString().c_str(), uid.toString().c_str(),
-                 toString(hasPermission), displayId.toString().c_str(),
-                 mTouchModePerDisplay.count(displayId) == 0
-                         ? "not set"
-                         : std::to_string(mTouchModePerDisplay[displayId]).c_str());
+        LOG_IF(INFO, DEBUG_TOUCH_MODE)
+                << "Request to change touch mode to " << toString(inTouchMode)
+                << " (calling pid=" << pid.toString() << ", uid=" << uid.toString()
+                << ", hasPermission=" << toString(hasPermission)
+                << ", target displayId=" << displayId.toString()
+                << ", mTouchModePerDisplay[displayId]="
+                << (mTouchModePerDisplay.count(displayId) == 0
+                            ? "not set"
+                            : std::to_string(mTouchModePerDisplay[displayId]))
+                << ")";
 
         auto touchModeIt = mTouchModePerDisplay.find(displayId);
         if (touchModeIt != mTouchModePerDisplay.end() && touchModeIt->second == inTouchMode) {
@@ -5798,40 +5747,10 @@
     mWindowInfos.setMaximumObscuringOpacityForTouch(opacity);
 }
 
-std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
-InputDispatcher::findTouchStateWindowAndDisplay(
-        const sp<IBinder>& token,
-        const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) {
-    for (auto& [displayId, state] : touchStatesByDisplay) {
-        for (const TouchedWindow& w : state.windows) {
-            if (w.windowHandle->getToken() == token) {
-                return std::make_tuple(&state, &w, displayId);
-            }
-        }
-    }
-    return std::make_tuple(nullptr, nullptr, ui::LogicalDisplayId::DEFAULT);
-}
-
-std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
-InputDispatcher::findTouchStateWindowAndDisplay(
-        const sp<IBinder>& token,
-        std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay) {
-    auto [constTouchState, constTouchedWindow, displayId] = InputDispatcher::
-            findTouchStateWindowAndDisplay(token,
-                                           const_cast<const std::unordered_map<ui::LogicalDisplayId,
-                                                                               TouchState>&>(
-                                                   touchStatesByDisplay));
-
-    return std::make_tuple(const_cast<TouchState*>(constTouchState),
-                           const_cast<TouchedWindow*>(constTouchedWindow), displayId);
-}
-
 bool InputDispatcher::transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
                                            bool isDragDrop) {
     if (fromToken == toToken) {
-        if (DEBUG_FOCUS) {
-            ALOGD("Trivial transfer to same window.");
-        }
+        LOG_IF(INFO, DEBUG_FOCUS) << "Trivial transfer to same window.";
         return true;
     }
 
@@ -5849,7 +5768,7 @@
             return false;
         }
 
-        const auto [toWindowHandle, deviceId, pointers, cancellations, pointerDowns] =
+        const auto& [toWindowHandle, deviceId, pointers, cancellations, pointerDowns] =
                 result.value();
 
         for (const auto& cancellationArgs : cancellations) {
@@ -5914,10 +5833,8 @@
         return std::nullopt;
     }
 
-    if (DEBUG_FOCUS) {
-        ALOGD("%s: fromWindowHandle=%s, toWindowHandle=%s", __func__,
-              fromWindowHandle->getName().c_str(), toWindowHandle->getName().c_str());
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << __func__ << ": fromWindowHandle=" << fromWindowHandle->getName()
+                              << ", toWindowHandle=" << toWindowHandle->getName();
 
     // Erase old window.
     ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow.targetFlags;
@@ -5970,26 +5887,25 @@
  */
 sp<WindowInfoHandle> InputDispatcher::DispatcherTouchState::findTouchedForegroundWindow(
         ui::LogicalDisplayId displayId) const {
-    const auto stateIt = mTouchStatesByDisplay.find(displayId);
-    if (stateIt == mTouchStatesByDisplay.end()) {
-        ALOGI("No touch state on display %s", displayId.toString().c_str());
-        return nullptr;
-    }
-
-    const TouchState& state = stateIt->second;
     sp<WindowInfoHandle> touchedForegroundWindow;
-    // If multiple foreground windows are touched, return nullptr
-    for (const TouchedWindow& window : state.windows) {
-        if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
-            if (touchedForegroundWindow != nullptr) {
-                ALOGI("Two or more foreground windows: %s and %s",
-                      touchedForegroundWindow->getName().c_str(),
-                      window.windowHandle->getName().c_str());
-                return nullptr;
+    forTouchAndCursorStatesOnDisplay(displayId, [&](const TouchState& state) {
+        // If multiple foreground windows are touched, return nullptr
+        for (const TouchedWindow& window : state.windows) {
+            if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
+                if (touchedForegroundWindow != nullptr) {
+                    ALOGI("Two or more foreground windows: %s and %s",
+                          touchedForegroundWindow->getName().c_str(),
+                          window.windowHandle->getName().c_str());
+                    touchedForegroundWindow = nullptr;
+                    return true;
+                }
+                touchedForegroundWindow = window.windowHandle;
             }
-            touchedForegroundWindow = window.windowHandle;
         }
-    }
+        return false;
+    });
+    ALOGI_IF(touchedForegroundWindow == nullptr,
+             "No touch state or no touched foreground window on display %d", displayId.val());
     return touchedForegroundWindow;
 }
 
@@ -6020,9 +5936,7 @@
 }
 
 void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
-    if (DEBUG_FOCUS) {
-        ALOGD("Resetting and dropping all events (%s).", reason);
-    }
+    LOG_IF(INFO, DEBUG_FOCUS) << "Resetting and dropping all events (" << reason << ").";
 
     ScopedSyntheticEventTracer traceContext(mTracer);
     CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason,
@@ -6177,9 +6091,7 @@
 };
 
 Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
-    if (DEBUG_CHANNEL_CREATION) {
-        ALOGD("channel '%s' ~ createInputChannel", name.c_str());
-    }
+    LOG_IF(INFO, DEBUG_CHANNEL_CREATION) << "channel '" << name << "' ~ createInputChannel";
 
     std::unique_ptr<InputChannel> serverChannel;
     std::unique_ptr<InputChannel> clientChannel;
@@ -6295,14 +6207,14 @@
         return BAD_VALUE;
     }
 
-    ScopedSyntheticEventTracer traceContext(mTracer);
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                               "input channel stole pointer stream", traceContext.getTracker());
     const auto result = mTouchStates.pilferPointers(token, *requestingConnection);
     if (!result.ok()) {
         return result.error().code();
     }
 
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                               "input channel stole pointer stream", traceContext.getTracker());
     const auto cancellations = *result;
     for (const auto& cancellationArgs : cancellations) {
         LOG_ALWAYS_FATAL_IF(cancellationArgs.mode !=
@@ -6730,12 +6642,11 @@
         // then cancel the associated fallback key, if any.
         if (fallbackKeyCode) {
             // Dispatch the unhandled key to the policy with the cancel flag.
-            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                ALOGD("Unhandled key event: Asking policy to cancel fallback action.  "
-                      "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-                      keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount,
-                      keyEntry.policyFlags);
-            }
+            LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                    << "Unhandled key event: Asking policy to cancel fallback action.  keyCode="
+                    << keyEntry.keyCode << ", action=" << keyEntry.action
+                    << ", repeatCount=" << keyEntry.repeatCount << ", policyFlags=0x" << std::hex
+                    << keyEntry.policyFlags;
             KeyEvent event = createKeyEvent(keyEntry);
             event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);
 
@@ -6772,21 +6683,22 @@
         // Then ask the policy what to do with it.
         bool initialDown = keyEntry.action == AKEY_EVENT_ACTION_DOWN && keyEntry.repeatCount == 0;
         if (!fallbackKeyCode && !initialDown) {
-            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                ALOGD("Unhandled key event: Skipping unhandled key event processing "
-                      "since this is not an initial down.  "
-                      "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-                      originalKeyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
-            }
+            LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                    << "Unhandled key event: Skipping unhandled key event processing since this is "
+                       "not an initial down.  keyCode="
+                    << originalKeyCode << ", action=" << keyEntry.action
+                    << ", repeatCount=" << keyEntry.repeatCount << ", policyFlags=0x" << std::hex
+                    << keyEntry.policyFlags;
             return {};
         }
 
         // Dispatch the unhandled key to the policy.
-        if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-            ALOGD("Unhandled key event: Asking policy to perform fallback action.  "
-                  "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-                  keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
-        }
+        LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                << "Unhandled key event: Asking policy to perform fallback action.  keyCode="
+                << keyEntry.keyCode << ", action=" << keyEntry.action
+                << ", repeatCount=" << keyEntry.repeatCount << ", policyFlags=0x" << std::hex
+                << keyEntry.policyFlags;
+        ;
         KeyEvent event = createKeyEvent(keyEntry);
 
         mLock.unlock();
@@ -6883,16 +6795,13 @@
                 newEntry->traceTracker =
                         mTracer->traceDerivedEvent(*newEntry, *keyEntry.traceTracker);
             }
-            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                ALOGD("Unhandled key event: Dispatching fallback key.  "
-                      "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
-                      originalKeyCode, *fallbackKeyCode, keyEntry.metaState);
-            }
+            LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS)
+                    << "Unhandled key event: Dispatching fallback key.  originalKeyCode="
+                    << originalKeyCode << ", fallbackKeyCode=" << *fallbackKeyCode
+                    << ", fallbackMetaState=0x" << std::hex << keyEntry.metaState;
             return newEntry;
         } else {
-            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-                ALOGD("Unhandled key event: No fallback key.");
-            }
+            LOG_IF(INFO, DEBUG_OUTBOUND_EVENT_DETAILS) << "Unhandled key event: No fallback key.";
 
             // Report the key as unhandled, since there is no fallback key.
             mReporter->reportUnhandledKey(keyEntry.id);
@@ -7033,7 +6942,7 @@
         return;
     }
 
-    ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
+    LOG_IF(INFO, DEBUG_FOCUS) << "Disabling Pointer Capture because the window lost focus.";
 
     if (mCurrentPointerCaptureRequest.isEnable()) {
         setPointerCaptureLocked(nullptr);
@@ -7201,7 +7110,8 @@
                                                      oldTouchedWindow.targetFlags,
                                                      getPointerIds(pointers),
                                                      oldTouchedWindow.getDownTimeInTarget(deviceId),
-                                                     connections, windowInfos, dump, targets);
+                                                     /*pointerDisplayId=*/std::nullopt, connections,
+                                                     windowInfos, dump, targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, oldWallpaper);
     }
 
@@ -7310,6 +7220,12 @@
     }
 }
 
+void InputDispatcher::setDisplayTopology(
+        const android::DisplayTopologyGraph& displayTopologyGraph) {
+    std::scoped_lock _l(mLock);
+    mWindowInfos.setDisplayTopology(displayTopologyGraph);
+}
+
 InputDispatcher::ConnectionManager::ConnectionManager(const sp<android::Looper>& looper)
       : mLooper(looper) {}
 
@@ -7445,6 +7361,11 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
+void InputDispatcher::DispatcherWindowInfo::setDisplayTopology(
+        const DisplayTopologyGraph& displayTopologyGraph) {
+    mTopology = displayTopologyGraph;
+}
+
 ftl::Flags<InputTarget::Flags> InputDispatcher::DispatcherTouchState::getTargetFlags(
         const sp<WindowInfoHandle>& targetWindow, vec2 targetPosition, bool isSplit,
         const DispatcherWindowInfo& windowInfos) {
@@ -7466,67 +7387,227 @@
 
 bool InputDispatcher::DispatcherTouchState::hasTouchingOrHoveringPointers(
         ui::LogicalDisplayId displayId, int32_t deviceId) const {
-    const auto touchStateIt = mTouchStatesByDisplay.find(displayId);
-    if (touchStateIt == mTouchStatesByDisplay.end()) {
-        return false;
-    }
-    return touchStateIt->second.hasTouchingPointers(deviceId) ||
-            touchStateIt->second.hasHoveringPointers(deviceId);
+    bool hasTouchingOrHoveringPointers = false;
+    forTouchAndCursorStatesOnDisplay(displayId, [&](const TouchState& state) {
+        hasTouchingOrHoveringPointers =
+                state.hasTouchingPointers(deviceId) || state.hasHoveringPointers(deviceId);
+        return hasTouchingOrHoveringPointers;
+    });
+    return hasTouchingOrHoveringPointers;
 }
 
 bool InputDispatcher::DispatcherTouchState::isPointerInWindow(const sp<android::IBinder>& token,
                                                               ui::LogicalDisplayId displayId,
                                                               android::DeviceId deviceId,
                                                               int32_t pointerId) const {
-    const auto touchStateIt = mTouchStatesByDisplay.find(displayId);
-    if (touchStateIt == mTouchStatesByDisplay.end()) {
-        return false;
-    }
-    for (const TouchedWindow& window : touchStateIt->second.windows) {
-        if (window.windowHandle->getToken() == token &&
-            (window.hasTouchingPointer(deviceId, pointerId) ||
-             window.hasHoveringPointer(deviceId, pointerId))) {
-            return true;
+    bool isPointerInWindow = false;
+    forTouchAndCursorStatesOnDisplay(displayId, [&](const TouchState& state) {
+        for (const TouchedWindow& window : state.windows) {
+            if (window.windowHandle->getToken() == token &&
+                (window.hasTouchingPointer(deviceId, pointerId) ||
+                 window.hasHoveringPointer(deviceId, pointerId))) {
+                isPointerInWindow = true;
+                return true;
+            }
         }
-    }
-    return false;
+        return false;
+    });
+    return isPointerInWindow;
+}
+
+std::tuple<const sp<gui::WindowInfoHandle>&, ui::LogicalDisplayId>
+InputDispatcher::DispatcherTouchState::findExistingTouchedWindowHandleAndDisplay(
+        const sp<android::IBinder>& token) const {
+    std::optional<std::tuple<const sp<gui::WindowInfoHandle>&, ui::LogicalDisplayId>>
+            touchedWindowHandleAndDisplay;
+    forAllTouchAndCursorStates([&](ui::LogicalDisplayId displayId, const TouchState& state) {
+        for (const TouchedWindow& w : state.windows) {
+            if (w.windowHandle->getToken() == token) {
+                touchedWindowHandleAndDisplay.emplace(std::ref(w.windowHandle), displayId);
+                return true;
+            }
+        }
+        return false;
+    });
+    LOG_ALWAYS_FATAL_IF(!touchedWindowHandleAndDisplay.has_value(),
+                        "%s : Touch state is out of sync: No touched window for token", __func__);
+    return touchedWindowHandleAndDisplay.value();
+}
+
+void InputDispatcher::DispatcherTouchState::forAllTouchedWindows(
+        std::function<void(const sp<gui::WindowInfoHandle>&)> f) const {
+    forAllTouchAndCursorStates([&](ui::LogicalDisplayId displayId, const TouchState& state) {
+        for (const TouchedWindow& window : state.windows) {
+            f(window.windowHandle);
+        }
+        return false;
+    });
+}
+
+void InputDispatcher::DispatcherTouchState::forAllTouchedWindowsOnDisplay(
+        ui::LogicalDisplayId displayId,
+        std::function<void(const sp<gui::WindowInfoHandle>&)> f) const {
+    forTouchAndCursorStatesOnDisplay(displayId, [&](const TouchState& state) {
+        for (const TouchedWindow& window : state.windows) {
+            f(window.windowHandle);
+        }
+        return false;
+    });
 }
 
 std::string InputDispatcher::DispatcherTouchState::dump() const {
     std::string dump;
-    if (!mTouchStatesByDisplay.empty()) {
-        dump += StringPrintf("TouchStatesByDisplay:\n");
+    if (mTouchStatesByDisplay.empty()) {
+        dump += "TouchStatesByDisplay: <no displays touched>\n";
+    } else {
+        dump += "TouchStatesByDisplay:\n";
         for (const auto& [displayId, state] : mTouchStatesByDisplay) {
             std::string touchStateDump = addLinePrefix(state.dump(), INDENT);
             dump += INDENT + displayId.toString() + " : " + touchStateDump;
         }
+    }
+    if (mCursorStateByDisplay.empty()) {
+        dump += "CursorStatesByDisplay: <no displays touched by cursor>\n";
     } else {
-        dump += "TouchStates: <no displays touched>\n";
+        dump += "CursorStatesByDisplay:\n";
+        for (const auto& [displayId, state] : mCursorStateByDisplay) {
+            std::string touchStateDump = addLinePrefix(state.dump(), INDENT);
+            dump += INDENT + displayId.toString() + " : " + touchStateDump;
+        }
     }
     return dump;
 }
 
 void InputDispatcher::DispatcherTouchState::removeAllPointersForDevice(android::DeviceId deviceId) {
-    for (auto& [_, touchState] : mTouchStatesByDisplay) {
-        touchState.removeAllPointersForDevice(deviceId);
-    }
+    forAllTouchAndCursorStates([&](ui::LogicalDisplayId displayId, TouchState& state) {
+        state.removeAllPointersForDevice(deviceId);
+        return false;
+    });
 }
 
 void InputDispatcher::DispatcherTouchState::clear() {
     mTouchStatesByDisplay.clear();
+    mCursorStateByDisplay.clear();
+}
+
+void InputDispatcher::DispatcherTouchState::saveTouchStateForMotionEntry(
+        const android::inputdispatcher::MotionEntry& entry,
+        android::inputdispatcher::TouchState&& touchState,
+        const DispatcherWindowInfo& windowInfos) {
+    if (touchState.windows.empty()) {
+        eraseTouchStateForMotionEntry(entry, windowInfos);
+        return;
+    }
+
+    if (com::android::input::flags::connected_displays_cursor() &&
+        isMouseOrTouchpad(entry.source)) {
+        mCursorStateByDisplay[windowInfos.getPrimaryDisplayId(entry.displayId)] =
+                std::move(touchState);
+    } else {
+        mTouchStatesByDisplay[entry.displayId] = std::move(touchState);
+    }
+}
+
+void InputDispatcher::DispatcherTouchState::eraseTouchStateForMotionEntry(
+        const android::inputdispatcher::MotionEntry& entry,
+        const DispatcherWindowInfo& windowInfos) {
+    if (com::android::input::flags::connected_displays_cursor() &&
+        isMouseOrTouchpad(entry.source)) {
+        mCursorStateByDisplay.erase(windowInfos.getPrimaryDisplayId(entry.displayId));
+    } else {
+        mTouchStatesByDisplay.erase(entry.displayId);
+    }
+}
+
+const TouchState* InputDispatcher::DispatcherTouchState::getTouchStateForMotionEntry(
+        const android::inputdispatcher::MotionEntry& entry,
+        const DispatcherWindowInfo& windowInfos) const {
+    if (com::android::input::flags::connected_displays_cursor() &&
+        isMouseOrTouchpad(entry.source)) {
+        auto touchStateIt =
+                mCursorStateByDisplay.find(windowInfos.getPrimaryDisplayId(entry.displayId));
+        if (touchStateIt != mCursorStateByDisplay.end()) {
+            return &touchStateIt->second;
+        }
+    } else {
+        auto touchStateIt = mTouchStatesByDisplay.find(entry.displayId);
+        if (touchStateIt != mTouchStatesByDisplay.end()) {
+            return &touchStateIt->second;
+        }
+    }
+    return nullptr;
+}
+
+void InputDispatcher::DispatcherTouchState::forTouchAndCursorStatesOnDisplay(
+        ui::LogicalDisplayId displayId, std::function<bool(const TouchState&)> f) const {
+    const auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+    if (touchStateIt != mTouchStatesByDisplay.end() && f(touchStateIt->second)) {
+        return;
+    }
+
+    // TODO(b/383092013): This is currently not accounting for the "topology group" concept.
+    // Proper implementation requires looking tghrough all the displays in the topology group.
+    const auto cursorStateIt = mCursorStateByDisplay.find(displayId);
+    if (cursorStateIt != mCursorStateByDisplay.end()) {
+        f(cursorStateIt->second);
+    }
+}
+
+void InputDispatcher::DispatcherTouchState::forTouchAndCursorStatesOnDisplay(
+        ui::LogicalDisplayId displayId, std::function<bool(TouchState&)> f) {
+    const_cast<const DispatcherTouchState&>(*this)
+            .forTouchAndCursorStatesOnDisplay(displayId, [&](const TouchState& state) {
+                return f(const_cast<TouchState&>(state));
+            });
+}
+
+void InputDispatcher::DispatcherTouchState::forAllTouchAndCursorStates(
+        std::function<bool(ui::LogicalDisplayId, const TouchState&)> f) const {
+    for (auto& [displayId, state] : mTouchStatesByDisplay) {
+        if (f(displayId, state)) {
+            return;
+        }
+    }
+    for (auto& [displayId, state] : mCursorStateByDisplay) {
+        if (f(displayId, state)) {
+            return;
+        }
+    }
+}
+
+void InputDispatcher::DispatcherTouchState::forAllTouchAndCursorStates(
+        std::function<bool(ui::LogicalDisplayId, TouchState&)> f) {
+    const_cast<const DispatcherTouchState&>(*this).forAllTouchAndCursorStates(
+            [&](ui::LogicalDisplayId displayId, const TouchState& constState) {
+                return f(displayId, const_cast<TouchState&>(constState));
+            });
 }
 
 std::optional<std::tuple<TouchState&, TouchedWindow&, ui::LogicalDisplayId>>
 InputDispatcher::DispatcherTouchState::findTouchStateWindowAndDisplay(
         const sp<android::IBinder>& token) {
-    for (auto& [displayId, state] : mTouchStatesByDisplay) {
+    std::optional<std::tuple<TouchState&, TouchedWindow&, ui::LogicalDisplayId>>
+            touchStateWindowAndDisplay;
+    forAllTouchAndCursorStates([&](ui::LogicalDisplayId displayId, TouchState& state) {
         for (TouchedWindow& w : state.windows) {
             if (w.windowHandle->getToken() == token) {
-                return std::make_tuple(std::ref(state), std::ref(w), displayId);
+                touchStateWindowAndDisplay.emplace(std::ref(state), std::ref(w), displayId);
+                return true;
             }
         }
+        return false;
+    });
+    return touchStateWindowAndDisplay;
+}
+
+bool InputDispatcher::DispatcherTouchState::isStylusActiveInDisplay(
+        ui::LogicalDisplayId displayId) const {
+    const auto it = mTouchStatesByDisplay.find(displayId);
+    if (it == mTouchStatesByDisplay.end()) {
+        return false;
     }
-    return std::nullopt;
+    const TouchState& state = it->second;
+    return state.hasActiveStylus();
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index f214131..a949900 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -164,6 +164,8 @@
 
     void setInputMethodConnectionIsActive(bool isActive) override;
 
+    void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) override;
+
 private:
     enum class DropReason {
         NOT_DROPPED,
@@ -291,6 +293,8 @@
 
         void setMaximumObscuringOpacityForTouch(float opacity);
 
+        void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
+
         // Get a reference to window handles by display, return an empty vector if not found.
         const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesForDisplay(
                 ui::LogicalDisplayId displayId) const;
@@ -303,8 +307,11 @@
         // Get the transform for display, returns Identity-transform if display is missing.
         ui::Transform getDisplayTransform(ui::LogicalDisplayId displayId) const;
 
-        // Get the raw transform to use for motion events going to the given window.
-        ui::Transform getRawTransform(const android::gui::WindowInfo&) const;
+        // Get the raw transform to use for motion events going to the given window. Optionally a
+        // pointer displayId may be supplied if pointer is on a different display from the window.
+        ui::Transform getRawTransform(
+                const android::gui::WindowInfo& windowInfo,
+                std::optional<ui::LogicalDisplayId> pointerDisplayId = std::nullopt) const;
 
         // Lookup for WindowInfoHandle from token and optionally a display-id. In cases where
         // display-id is not provided lookup is done for all displays.
@@ -319,11 +326,6 @@
                 ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
                 const sp<android::gui::WindowInfoHandle> ignoreWindow = nullptr) const;
 
-        std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAt(
-                ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
-                const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay)
-                const;
-
         TouchOcclusionInfo computeTouchOcclusionInfo(
                 const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const;
 
@@ -337,6 +339,10 @@
 
         bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const;
 
+        // Returns topology's primary display if the display belongs to it, otherwise the
+        // same displayId.
+        ui::LogicalDisplayId getPrimaryDisplayId(ui::LogicalDisplayId displayId) const;
+
         std::string dumpDisplayAndWindowInfo() const;
 
     private:
@@ -346,6 +352,11 @@
         std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo>
                 mDisplayInfos;
         float mMaximumObscuringOpacityForTouch{1.0f};
+
+        // Topology is initialized with default-constructed value, which is an empty topology until
+        // we receive setDisplayTopology call. Meanwhile we will treat every display as an
+        // independent display.
+        DisplayTopologyGraph mTopology;
     };
 
     DispatcherWindowInfo mWindowInfos GUARDED_BY(mLock);
@@ -371,6 +382,7 @@
                                            ftl::Flags<InputTarget::Flags> targetFlags,
                                            std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                            std::optional<nsecs_t> firstDownTimeInTarget,
+                                           std::optional<ui::LogicalDisplayId> pointerDisplayId,
                                            const ConnectionManager& connections,
                                            const DispatcherWindowInfo& windowInfos,
                                            std::function<void()> dump,
@@ -392,6 +404,16 @@
         bool isPointerInWindow(const sp<android::IBinder>& token, ui::LogicalDisplayId displayId,
                                DeviceId deviceId, int32_t pointerId) const;
 
+        // Find an existing touched windowHandle and display by token.
+        std::tuple<const sp<gui::WindowInfoHandle>&, ui::LogicalDisplayId>
+        findExistingTouchedWindowHandleAndDisplay(const sp<IBinder>& token) const;
+
+        void forAllTouchedWindows(std::function<void(const sp<gui::WindowInfoHandle>&)> f) const;
+
+        void forAllTouchedWindowsOnDisplay(
+                ui::LogicalDisplayId displayId,
+                std::function<void(const sp<gui::WindowInfoHandle>&)> f) const;
+
         std::string dump() const;
 
         // Updates the touchState for display from WindowInfo,
@@ -415,9 +437,32 @@
 
         void clear();
 
+    private:
         std::unordered_map<ui::LogicalDisplayId, TouchState> mTouchStatesByDisplay;
 
-    private:
+        // As there can be only one CursorState per topology group, we will treat all displays in
+        // the topology as one connected display-group. These will be identified by
+        // DisplayTopologyGraph::primaryDisplayId.
+        // Cursor on the any of the displays that are not part of the topology will be identified by
+        // the displayId similar to mTouchStatesByDisplay.
+        std::unordered_map<ui::LogicalDisplayId, TouchState> mCursorStateByDisplay;
+
+        // The supplied lambda is invoked for each touch and cursor state of the display.
+        // The function iterates until the lambda returns true, effectively performing a 'break'
+        // from the iteration.
+        void forTouchAndCursorStatesOnDisplay(ui::LogicalDisplayId displayId,
+                                              std::function<bool(const TouchState&)> f) const;
+
+        void forTouchAndCursorStatesOnDisplay(ui::LogicalDisplayId displayId,
+                                              std::function<bool(TouchState&)> f);
+
+        // The supplied lambda is invoked for each touchState. The function iterates until
+        // the lambda returns true, effectively performing a 'break' from the iteration.
+        void forAllTouchAndCursorStates(
+                std::function<bool(ui::LogicalDisplayId, const TouchState&)> f) const;
+
+        void forAllTouchAndCursorStates(std::function<bool(ui::LogicalDisplayId, TouchState&)> f);
+
         std::optional<std::tuple<TouchState&, TouchedWindow&, ui::LogicalDisplayId>>
         findTouchStateWindowAndDisplay(const sp<IBinder>& token);
 
@@ -429,6 +474,25 @@
                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                 const DispatcherWindowInfo& windowInfos, const ConnectionManager& connections);
 
+        void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState,
+                                          const DispatcherWindowInfo& windowInfos);
+
+        void eraseTouchStateForMotionEntry(const MotionEntry& entry,
+                                           const DispatcherWindowInfo& windowInfos);
+
+        const TouchState* getTouchStateForMotionEntry(
+                const android::inputdispatcher::MotionEntry& entry,
+                const DispatcherWindowInfo& windowInfos) const;
+
+        bool canWindowReceiveMotion(const sp<gui::WindowInfoHandle>& window,
+                                    const MotionEntry& motionEntry,
+                                    const ConnectionManager& connections,
+                                    const DispatcherWindowInfo& windowInfos) const;
+
+        // Return true if stylus is currently down anywhere on the specified display,
+        // and false otherwise.
+        bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId) const;
+
         static std::list<CancellationArgs> eraseRemovedWindowsFromWindowInfo(
                 TouchState& state, ui::LogicalDisplayId displayId,
                 const DispatcherWindowInfo& windowInfos);
@@ -584,11 +648,6 @@
     sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(
             ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
-    static bool canWindowReceiveMotion(
-            const sp<android::gui::WindowInfoHandle>& window, const MotionEntry& motionEntry,
-            const ConnectionManager& connections, const DispatcherWindowInfo& windowInfos,
-            const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStates);
-
     // Returns all the input targets (with their respective input channels) from the window handles
     // passed as argument.
     std::vector<InputTarget> getInputTargetsFromWindowHandlesLocked(
@@ -758,6 +817,10 @@
     std::string getApplicationWindowLabel(const InputApplicationHandle* applicationHandle,
                                           const sp<android::gui::WindowInfoHandle>& windowHandle);
 
+    static std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAt(
+            ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
+            const DispatcherWindowInfo& windowInfos);
+
     static bool shouldDropInput(const EventEntry& entry,
                                 const sp<android::gui::WindowInfoHandle>& windowHandle,
                                 const DispatcherWindowInfo& windowInfo);
@@ -855,17 +918,6 @@
             const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
             bool handled) REQUIRES(mLock);
 
-    // Find touched state and touched window by token.
-    static std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
-    findTouchStateWindowAndDisplay(
-            const sp<IBinder>& token,
-            std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay);
-
-    static std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
-    findTouchStateWindowAndDisplay(
-            const sp<IBinder>& token,
-            const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay);
-
     // Statistics gathering.
     nsecs_t mLastStatisticPushTime = 0;
     std::unique_ptr<InputEventTimelineProcessor> mInputEventTimelineProcessor GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 9b5a79b..5abd65a 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,6 +24,17 @@
 
 namespace android::inputdispatcher {
 
+namespace {
+
+bool isMouseOrTouchpad(uint32_t sources) {
+    // Check if this is a mouse or touchpad, but not a drawing tablet.
+    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
+} // namespace
+
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
@@ -221,10 +232,15 @@
 }
 
 ssize_t InputState::findMotionMemento(const MotionEntry& entry, bool hovering) const {
+    // If we have connected displays a mouse can move between displays and displayId may change
+    // while a gesture is in-progress.
+    const bool skipDisplayCheck = com::android::input::flags::connected_displays_cursor() &&
+            isMouseOrTouchpad(entry.source);
     for (size_t i = 0; i < mMotionMementos.size(); i++) {
         const MotionMemento& memento = mMotionMementos[i];
         if (memento.deviceId == entry.deviceId && memento.source == entry.source &&
-            memento.displayId == entry.displayId && memento.hovering == hovering) {
+            memento.hovering == hovering &&
+            (skipDisplayCheck || memento.displayId == entry.displayId)) {
             return i;
         }
     }
@@ -338,7 +354,10 @@
         // would receive different events from each display. Since the TouchStates are per-display,
         // it's unlikely that those two streams would be consistent with each other. Therefore,
         // cancel the previous gesture if the display id changes.
-        if (motionEntry.displayId != lastMemento.displayId) {
+        // Except when we have connected-displays where a mouse may move across display boundaries.
+        const bool skipDisplayCheck = (com::android::input::flags::connected_displays_cursor() &&
+                                       isMouseOrTouchpad(motionEntry.source));
+        if (!skipDisplayCheck && motionEntry.displayId != lastMemento.displayId) {
             LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
                       << " and new event is " << motionEntry;
             return true;
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 463a952..ab039c3 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -24,6 +24,7 @@
 #include <android/os/InputEventInjectionSync.h>
 #include <gui/InputApplication.h>
 #include <gui/WindowInfo.h>
+#include <input/DisplayTopologyGraph.h>
 #include <input/InputDevice.h>
 #include <input/InputTransport.h>
 #include <unordered_map>
@@ -243,6 +244,11 @@
      * Notify the dispatcher that the state of the input method connection changed.
      */
     virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
+
+    /*
+     * Notify the dispatcher of the latest DisplayTopology.
+     */
+    virtual void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/docs/device_configuration.md b/services/inputflinger/docs/device_configuration.md
new file mode 100644
index 0000000..0b75eb2
--- /dev/null
+++ b/services/inputflinger/docs/device_configuration.md
@@ -0,0 +1,10 @@
+# Input Device Configuration
+
+There are a number of properties that can be specified for an input device.
+
+|Property|Value|
+|---|----|
+|`audio.mic`|A boolean (`0` or `1`) that indicates whether the device has a microphone.|
+|`device.additionalSysfsLedsNode`|A string representing the path to search for device lights to be used in addition to searching the device node itself for lights.|
+|`device.internal`|A boolean (`0` or `1`) that indicates if this input device is part of the device as opposed to be externally attached.|
+|`device.type`|A string representing if the device is of a certain type. Valid values include `rotaryEncoder` and `externalStylus`.
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 36614b2..c805b74 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -61,6 +61,16 @@
 
     /* Notifies that mouse cursor faded due to typing. */
     virtual void notifyMouseCursorFadedOnTyping() = 0;
+
+    /**
+     * Give accessibility a chance to filter motion event by pointer devices.
+     * The return values denotes the delta x and y after filtering it.
+     *
+     * This call happens on the input hot path and it is extremely performance sensitive.
+     * This also must not call back into native code.
+     */
+    virtual std::optional<vec2> filterPointerMotionForAccessibility(
+            const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 013ef86..2fcb5d8 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -351,6 +351,22 @@
     return colors;
 }
 
+static base::Result<std::shared_ptr<PropertyMap>> loadConfiguration(
+        const InputDeviceIdentifier& ident) {
+    std::string configurationFile =
+            getInputDeviceConfigurationFilePathByDeviceIdentifier(ident,
+                                                                  InputDeviceConfigurationFileType::
+                                                                          CONFIGURATION);
+    if (configurationFile.empty()) {
+        ALOGD("No input device configuration file found for device '%s'.", ident.name.c_str());
+        return base::Result<std::shared_ptr<PropertyMap>>(nullptr);
+    }
+    base::Result<std::shared_ptr<PropertyMap>> propertyMap =
+            PropertyMap::load(configurationFile.c_str());
+
+    return propertyMap;
+}
+
 /**
  * Read country code information exposed through the sysfs path and convert it to Layout info.
  */
@@ -409,11 +425,22 @@
  *  Read information about lights exposed through the sysfs path.
  */
 static std::unordered_map<int32_t /*lightId*/, RawLightInfo> readLightsConfiguration(
-        const std::filesystem::path& sysfsRootPath) {
+        const std::filesystem::path& sysfsRootPath, const std::shared_ptr<PropertyMap>& config) {
     std::unordered_map<int32_t, RawLightInfo> lightInfos;
     int32_t nextLightId = 0;
-    // Check if device has any lights.
-    const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS);
+    // Check if device has any lights.  If the Input Device Configuration file specifies any lights,
+    // use those in addition to searching the device node itself for lights.
+    std::vector<std::filesystem::path> paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS);
+
+    if (config) {
+        auto additionalLights = config->getString("device.additionalSysfsLedsNode");
+        if (additionalLights) {
+            ALOGI("IDC specifies additional path for lights at '%s'",
+                  additionalLights.value().c_str());
+            paths.push_back(std::filesystem::path(additionalLights.value()));
+        }
+    }
+
     for (const auto& nodePath : paths) {
         RawLightInfo info;
         info.id = ++nextLightId;
@@ -532,17 +559,16 @@
 // --- EventHub::Device ---
 
 EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier,
-                         std::shared_ptr<const AssociatedDevice> assocDev)
+                         std::shared_ptr<PropertyMap> config)
       : fd(fd),
         id(id),
         path(std::move(path)),
         identifier(std::move(identifier)),
         classes(0),
-        configuration(nullptr),
+        configuration(std::move(config)),
         virtualKeyMap(nullptr),
         ffEffectPlaying(false),
         ffEffectId(-1),
-        associatedDevice(std::move(assocDev)),
         controllerNumber(0),
         enabled(true),
         isVirtual(fd < 0),
@@ -696,26 +722,6 @@
     return false;
 }
 
-void EventHub::Device::loadConfigurationLocked() {
-    configurationFile =
-            getInputDeviceConfigurationFilePathByDeviceIdentifier(identifier,
-                                                                  InputDeviceConfigurationFileType::
-                                                                          CONFIGURATION);
-    if (configurationFile.empty()) {
-        ALOGD("No input device configuration file found for device '%s'.", identifier.name.c_str());
-    } else {
-        android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
-                PropertyMap::load(configurationFile.c_str());
-        if (!propertyMap.ok()) {
-            ALOGE("Error loading input device configuration file for device '%s'.  "
-                  "Using default configuration.",
-                  identifier.name.c_str());
-        } else {
-            configuration = std::move(*propertyMap);
-        }
-    }
-}
-
 bool EventHub::Device::loadVirtualKeyMapLocked() {
     // The virtual key map is supplied by the kernel as a system board property file.
     std::string propPath = "/sys/board_properties/virtualkeys.";
@@ -1611,7 +1617,7 @@
 }
 
 std::shared_ptr<const EventHub::AssociatedDevice> EventHub::obtainAssociatedDeviceLocked(
-        const std::filesystem::path& devicePath) const {
+        const std::filesystem::path& devicePath, const std::shared_ptr<PropertyMap>& config) const {
     const std::optional<std::filesystem::path> sysfsRootPathOpt =
             getSysfsRootPath(devicePath.c_str());
     if (!sysfsRootPathOpt) {
@@ -1620,41 +1626,50 @@
 
     const auto& path = *sysfsRootPathOpt;
 
-    std::shared_ptr<const AssociatedDevice> associatedDevice = std::make_shared<AssociatedDevice>(
-            AssociatedDevice{.sysfsRootPath = path,
-                             .batteryInfos = readBatteryConfiguration(path),
-                             .lightInfos = readLightsConfiguration(path),
-                             .layoutInfo = readLayoutConfiguration(path)});
-
-    bool associatedDeviceChanged = false;
+    std::shared_ptr<const AssociatedDevice> associatedDevice;
     for (const auto& [id, dev] : mDevices) {
-        if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) {
-            if (*associatedDevice != *dev->associatedDevice) {
-                associatedDeviceChanged = true;
-                dev->associatedDevice = associatedDevice;
-            }
-            associatedDevice = dev->associatedDevice;
+        if (!dev->associatedDevice || dev->associatedDevice->sysfsRootPath != path) {
+            continue;
         }
+        if (!associatedDevice) {
+            // Found matching associated device for the first time.
+            associatedDevice = dev->associatedDevice;
+            // Reload this associated device if needed.  Use the base device
+            // config.  Note that this will essentially arbitrarily pick one
+            // Device as the base for the AssociatedDevice configuration.  If
+            // there are multiple Device's that have a configuration for the
+            // AssociatedDevice, only one configuration will be chosen and will
+            // be used for all other AssociatedDevices for the same sysfs path.
+            const auto reloadedDevice = AssociatedDevice(path, associatedDevice->baseDevConfig);
+            if (reloadedDevice != *dev->associatedDevice) {
+                ALOGI("The AssociatedDevice changed for path '%s'. Using new AssociatedDevice: %s",
+                      path.c_str(), associatedDevice->dump().c_str());
+                associatedDevice = std::make_shared<AssociatedDevice>(std::move(reloadedDevice));
+            }
+        }
+        // Update the associatedDevice.
+        dev->associatedDevice = associatedDevice;
     }
-    ALOGI_IF(associatedDeviceChanged,
-             "The AssociatedDevice changed for path '%s'. Using new AssociatedDevice: %s",
-             path.c_str(), associatedDevice->dump().c_str());
+
+    if (!associatedDevice) {
+        // No existing associated device found for this path, so create a new one.
+        associatedDevice = std::make_shared<AssociatedDevice>(path, config);
+    }
 
     return associatedDevice;
 }
 
-bool EventHub::AssociatedDevice::isChanged() const {
-    std::unordered_map<int32_t, RawBatteryInfo> newBatteryInfos =
-            readBatteryConfiguration(sysfsRootPath);
-    std::unordered_map<int32_t, RawLightInfo> newLightInfos =
-            readLightsConfiguration(sysfsRootPath);
-    std::optional<RawLayoutInfo> newLayoutInfo = readLayoutConfiguration(sysfsRootPath);
+EventHub::AssociatedDevice::AssociatedDevice(const std::filesystem::path& sysfsRootPath,
+                                             std::shared_ptr<PropertyMap> config)
+      : sysfsRootPath(sysfsRootPath),
+        baseDevConfig(std::move(config)),
+        batteryInfos(readBatteryConfiguration(sysfsRootPath)),
+        lightInfos(readLightsConfiguration(sysfsRootPath, baseDevConfig)),
+        layoutInfo(readLayoutConfiguration(sysfsRootPath)) {}
 
-    if (newBatteryInfos == batteryInfos && newLightInfos == lightInfos &&
-        newLayoutInfo == layoutInfo) {
-        return false;
-    }
-    return true;
+std::string EventHub::AssociatedDevice::dump() const {
+    return StringPrintf("path=%s, numBatteries=%zu, numLight=%zu", sysfsRootPath.c_str(),
+                        batteryInfos.size(), lightInfos.size());
 }
 
 void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) {
@@ -2335,11 +2350,21 @@
     // Fill in the descriptor.
     assignDescriptorLocked(identifier);
 
+    // Load the configuration file for the device.
+    std::shared_ptr<PropertyMap> configuration = nullptr;
+    base::Result<std::shared_ptr<PropertyMap>> propertyMapResult = loadConfiguration(identifier);
+    if (!propertyMapResult.ok()) {
+        ALOGE("Error loading input device configuration file for device '%s'. "
+              "Using default configuration. Error: %s",
+              identifier.name.c_str(), propertyMapResult.error().message().c_str());
+    } else {
+        configuration = propertyMapResult.value();
+    }
+
     // Allocate device.  (The device object takes ownership of the fd at this point.)
     int32_t deviceId = mNextDeviceId++;
     std::unique_ptr<Device> device =
-            std::make_unique<Device>(fd, deviceId, devicePath, identifier,
-                                     obtainAssociatedDeviceLocked(devicePath));
+            std::make_unique<Device>(fd, deviceId, devicePath, identifier, configuration);
 
     ALOGV("add device %d: %s\n", deviceId, devicePath.c_str());
     ALOGV("  bus:        %04x\n"
@@ -2354,8 +2379,8 @@
     ALOGV("  driver:     v%d.%d.%d\n", driverVersion >> 16, (driverVersion >> 8) & 0xff,
           driverVersion & 0xff);
 
-    // Load the configuration file for the device.
-    device->loadConfigurationLocked();
+    // Obtain the associated device, if any.
+    device->associatedDevice = obtainAssociatedDeviceLocked(devicePath, device->configuration);
 
     // Figure out the kinds of events the device reports.
     device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
@@ -2646,33 +2671,57 @@
 void EventHub::sysfsNodeChanged(const std::string& sysfsNodePath) {
     std::scoped_lock _l(mLock);
 
-    // Check in opening devices
-    for (auto it = mOpeningDevices.begin(); it != mOpeningDevices.end(); it++) {
-        std::unique_ptr<Device>& device = *it;
-        if (device->associatedDevice &&
-            sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) !=
-                    std::string::npos &&
-            device->associatedDevice->isChanged()) {
-            it = mOpeningDevices.erase(it);
-            openDeviceLocked(device->path);
+    // Testing whether a sysfs node changed involves several syscalls, so use a cache to avoid
+    // testing the same node multiple times.
+    std::map<std::shared_ptr<const AssociatedDevice>, bool /*changed*/> testedDevices;
+    auto isAssociatedDeviceChanged = [&testedDevices, &sysfsNodePath](const Device& dev) {
+        if (!dev.associatedDevice) {
+            return false;
+        }
+        if (auto testedIt = testedDevices.find(dev.associatedDevice);
+            testedIt != testedDevices.end()) {
+            return testedIt->second;
+        }
+        // Cache miss
+        if (sysfsNodePath.find(dev.associatedDevice->sysfsRootPath.string()) == std::string::npos) {
+            testedDevices.emplace(dev.associatedDevice, false);
+            return false;
+        }
+        auto reloadedDevice = AssociatedDevice(dev.associatedDevice->sysfsRootPath,
+                                               dev.associatedDevice->baseDevConfig);
+        const bool changed = *dev.associatedDevice != reloadedDevice;
+        testedDevices.emplace(dev.associatedDevice, changed);
+        return changed;
+    };
+
+    std::set<Device*> devicesToClose;
+    std::set<std::string /*path*/> devicesToOpen;
+
+    // Check in opening devices. If its associated device changed,
+    // the device should be removed from mOpeningDevices and needs to be opened again.
+    std::erase_if(mOpeningDevices, [&](const auto& dev) {
+        if (isAssociatedDeviceChanged(*dev)) {
+            devicesToOpen.emplace(dev->path);
+            return true;
+        }
+        return false;
+    });
+
+    // Check in already added device. If its associated device changed,
+    // the device needs to be re-opened.
+    for (const auto& [id, dev] : mDevices) {
+        if (isAssociatedDeviceChanged(*dev)) {
+            devicesToOpen.emplace(dev->path);
+            devicesToClose.emplace(dev.get());
         }
     }
 
-    // Check in already added device
-    std::vector<Device*> devicesToReopen;
-    for (const auto& [id, device] : mDevices) {
-        if (device->associatedDevice &&
-            sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) !=
-                    std::string::npos &&
-            device->associatedDevice->isChanged()) {
-            devicesToReopen.push_back(device.get());
-        }
-    }
-    for (const auto& device : devicesToReopen) {
+    for (auto* device : devicesToClose) {
         closeDeviceLocked(*device);
-        openDeviceLocked(device->path);
     }
-    devicesToReopen.clear();
+    for (const auto& path : devicesToOpen) {
+        openDeviceLocked(path);
+    }
 }
 
 void EventHub::createVirtualKeyboardLocked() {
@@ -2972,9 +3021,4 @@
     std::unique_lock<std::mutex> lock(mLock);
 }
 
-std::string EventHub::AssociatedDevice::dump() const {
-    return StringPrintf("path=%s, numBatteries=%zu, numLight=%zu", sysfsRootPath.c_str(),
-                        batteryInfos.size(), lightInfos.size());
-}
-
 } // namespace android
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 207806d..845cab0 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -62,6 +62,28 @@
             identifier1.location == identifier2.location);
 }
 
+/**
+ * Determines if the device classes passed for two devices represent incompatible combinations
+ * that should not be merged into into a single InputDevice.
+ */
+
+bool isCompatibleSubDevice(ftl::Flags<InputDeviceClass> classes1,
+                           ftl::Flags<InputDeviceClass> classes2) {
+    if (!com::android::input::flags::prevent_merging_input_pointer_devices()) {
+        return true;
+    }
+
+    const ftl::Flags<InputDeviceClass> pointerFlags =
+            ftl::Flags<InputDeviceClass>{InputDeviceClass::TOUCH, InputDeviceClass::TOUCH_MT,
+                                         InputDeviceClass::CURSOR, InputDeviceClass::TOUCHPAD};
+
+    // Do not merge devices that both have any type of pointer event.
+    if (classes1.any(pointerFlags) && classes2.any(pointerFlags)) return false;
+
+    // Safe to merge
+    return true;
+}
+
 bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
     const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
     if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
@@ -174,9 +196,8 @@
         if (mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             if (now >= mNextTimeout) {
-                if (debugRawEvents()) {
-                    ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
-                }
+                ALOGD_IF(debugRawEvents(), "Timeout expired, latency=%0.3fms",
+                         (now - mNextTimeout) * 0.000001f);
                 mNextTimeout = LLONG_MAX;
                 mPendingArgs += timeoutExpiredLocked(now);
             }
@@ -241,9 +262,7 @@
                 }
                 batchSize += 1;
             }
-            if (debugRawEvents()) {
-                ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
-            }
+            ALOGD_IF(debugRawEvents(), "BatchSize: %zu Count: %zu", batchSize, count);
             out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
         } else {
             switch (rawEvent->type) {
@@ -271,7 +290,8 @@
     }
 
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
-    std::shared_ptr<InputDevice> device = createDeviceLocked(when, eventHubId, identifier);
+    ftl::Flags<InputDeviceClass> classes = mEventHub->getDeviceClasses(eventHubId);
+    std::shared_ptr<InputDevice> device = createDeviceLocked(when, eventHubId, identifier, classes);
 
     mPendingArgs += device->configure(when, mConfig, /*changes=*/{});
     mPendingArgs += device->reset(when);
@@ -354,12 +374,16 @@
 }
 
 std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
-        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) {
-    auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
-        const InputDeviceIdentifier identifier2 =
-                devicePair.second->getDeviceInfo().getIdentifier();
-        return isSubDevice(identifier, identifier2);
-    });
+        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier,
+        ftl::Flags<InputDeviceClass> classes) {
+    auto deviceIt =
+            std::find_if(mDevices.begin(), mDevices.end(), [identifier, classes](auto& devicePair) {
+                const InputDeviceIdentifier identifier2 =
+                        devicePair.second->getDeviceInfo().getIdentifier();
+                const ftl::Flags<InputDeviceClass> classes2 = devicePair.second->getClasses();
+                return isSubDevice(identifier, identifier2) &&
+                        isCompatibleSubDevice(classes, classes2);
+            });
 
     std::shared_ptr<InputDevice> device;
     if (deviceIt != mDevices.end()) {
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 7434ae4..df22890 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -78,10 +78,8 @@
     if (rawMaxBrightness != MAX_BRIGHTNESS) {
         brightness = brightness * ratio;
     }
-    if (DEBUG_LIGHT_DETAILS) {
-        ALOGD("getRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
-              brightness, ratio);
-    }
+    ALOGD_IF(DEBUG_LIGHT_DETAILS, "getRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f",
+             rawLightId, brightness, ratio);
     return brightness;
 }
 
@@ -97,10 +95,8 @@
     if (rawMaxBrightness != MAX_BRIGHTNESS) {
         brightness = ceil(brightness / ratio);
     }
-    if (DEBUG_LIGHT_DETAILS) {
-        ALOGD("setRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
-              brightness, ratio);
-    }
+    ALOGD_IF(DEBUG_LIGHT_DETAILS, "setRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f",
+             rawLightId, brightness, ratio);
     context.setLightBrightness(rawLightId, brightness);
 }
 
@@ -453,10 +449,9 @@
         if (rawInfo->flags.test(InputLightClass::GLOBAL)) {
             rawGlobalId = rawId;
         }
-        if (DEBUG_LIGHT_DETAILS) {
-            ALOGD("Light rawId %d name %s max %d flags %s \n", rawInfo->id, rawInfo->name.c_str(),
-                  rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS), rawInfo->flags.string().c_str());
-        }
+        ALOGD_IF(DEBUG_LIGHT_DETAILS, "Light rawId %d name %s max %d flags %s\n", rawInfo->id,
+                 rawInfo->name.c_str(), rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS),
+                 rawInfo->flags.string().c_str());
     }
 
     // Construct a player ID light
@@ -473,10 +468,8 @@
     }
     // Construct a RGB light for composed RGB light
     if (hasRedLed && hasGreenLed && hasBlueLed) {
-        if (DEBUG_LIGHT_DETAILS) {
-            ALOGD("Rgb light ids [%d, %d, %d] \n", rawRgbIds.at(LightColor::RED),
-                  rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
-        }
+        ALOGD_IF(DEBUG_LIGHT_DETAILS, "Rgb light ids [%d, %d, %d]\n", rawRgbIds.at(LightColor::RED),
+                 rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
         bool isKeyboardBacklight = keyboardBacklightIds.find(rawRgbIds.at(LightColor::RED)) !=
                         keyboardBacklightIds.end() &&
                 keyboardBacklightIds.find(rawRgbIds.at(LightColor::GREEN)) !=
@@ -518,9 +511,8 @@
         // If the node is multi-color led, construct a MULTI_COLOR light
         if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) &&
             rawInfo.flags.test(InputLightClass::MULTI_INTENSITY)) {
-            if (DEBUG_LIGHT_DETAILS) {
-                ALOGD("Multicolor light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
-            }
+            ALOGD_IF(DEBUG_LIGHT_DETAILS, "Multicolor light Id %d name %s\n", rawInfo.id,
+                     rawInfo.name.c_str());
             std::unique_ptr<Light> light =
                     std::make_unique<MultiColorLight>(getDeviceContext(), rawInfo.name, ++mNextId,
                                                       type, rawInfo.id);
@@ -528,9 +520,8 @@
             continue;
         }
         // Construct a Mono LED light
-        if (DEBUG_LIGHT_DETAILS) {
-            ALOGD("Mono light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
-        }
+        ALOGD_IF(DEBUG_LIGHT_DETAILS, "Mono light Id %d name %s\n", rawInfo.id,
+                 rawInfo.name.c_str());
         std::unique_ptr<Light> light = std::make_unique<MonoLight>(getDeviceContext(), rawInfo.name,
                                                                    ++mNextId, type, rawInfo.id);
 
@@ -552,10 +543,8 @@
         return false;
     }
     auto& light = it->second;
-    if (DEBUG_LIGHT_DETAILS) {
-        ALOGD("setLightColor lightId %d type %s color 0x%x", lightId,
-              ftl::enum_string(light->type).c_str(), color);
-    }
+    ALOGD_IF(DEBUG_LIGHT_DETAILS, "setLightColor lightId %d type %s color 0x%x", lightId,
+             ftl::enum_string(light->type).c_str(), color);
     return light->setLightColor(color);
 }
 
@@ -566,10 +555,8 @@
     }
     auto& light = it->second;
     std::optional<int32_t> color = light->getLightColor();
-    if (DEBUG_LIGHT_DETAILS) {
-        ALOGD("getLightColor lightId %d type %s color 0x%x", lightId,
-              ftl::enum_string(light->type).c_str(), color.value_or(0));
-    }
+    ALOGD_IF(DEBUG_LIGHT_DETAILS, "getLightColor lightId %d type %s color 0x%x", lightId,
+             ftl::enum_string(light->type).c_str(), color.value_or(0));
     return color;
 }
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 5839b4c..5dce074 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -21,6 +21,7 @@
 #include <filesystem>
 #include <functional>
 #include <map>
+#include <memory>
 #include <optional>
 #include <ostream>
 #include <string>
@@ -619,13 +620,16 @@
 private:
     // Holds information about the sysfs device associated with the Device.
     struct AssociatedDevice {
+        AssociatedDevice(const std::filesystem::path& sysfsRootPath,
+                         std::shared_ptr<PropertyMap> baseDevConfig);
         // The sysfs root path of the misc device.
         std::filesystem::path sysfsRootPath;
+        // The configuration of the base device.
+        std::shared_ptr<PropertyMap> baseDevConfig;
         std::unordered_map<int32_t /*batteryId*/, RawBatteryInfo> batteryInfos;
         std::unordered_map<int32_t /*lightId*/, RawLightInfo> lightInfos;
         std::optional<RawLayoutInfo> layoutInfo;
 
-        bool isChanged() const;
         bool operator==(const AssociatedDevice&) const = default;
         bool operator!=(const AssociatedDevice&) const = default;
         std::string dump() const;
@@ -658,7 +662,7 @@
         std::map<int /*axis*/, AxisState> absState;
 
         std::string configurationFile;
-        std::unique_ptr<PropertyMap> configuration;
+        std::shared_ptr<PropertyMap> configuration;
         std::unique_ptr<VirtualKeyMap> virtualKeyMap;
         KeyMap keyMap;
 
@@ -672,7 +676,7 @@
         int32_t controllerNumber;
 
         Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier,
-               std::shared_ptr<const AssociatedDevice> assocDev);
+               std::shared_ptr<PropertyMap> config);
         ~Device();
 
         void close();
@@ -692,7 +696,6 @@
         void populateAbsoluteAxisStates();
         bool hasKeycodeLocked(int keycode) const;
         bool hasKeycodeInternalLocked(int keycode) const;
-        void loadConfigurationLocked();
         bool loadVirtualKeyMapLocked();
         status_t loadKeyMapLocked();
         bool isExternalDeviceLocked();
@@ -724,7 +727,8 @@
     void addDeviceLocked(std::unique_ptr<Device> device) REQUIRES(mLock);
     void assignDescriptorLocked(InputDeviceIdentifier& identifier) REQUIRES(mLock);
     std::shared_ptr<const AssociatedDevice> obtainAssociatedDeviceLocked(
-            const std::filesystem::path& devicePath) const REQUIRES(mLock);
+            const std::filesystem::path& devicePath,
+            const std::shared_ptr<PropertyMap>& config) const REQUIRES(mLock);
 
     void closeDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock);
     void closeVideoDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 931766b..0d6e102 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -25,6 +25,7 @@
 #include <vector>
 
 #include "EventHub.h"
+#include "InputDevice.h"
 #include "InputListener.h"
 #include "InputReaderBase.h"
 #include "InputReaderContext.h"
@@ -127,7 +128,8 @@
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
-                                                            const InputDeviceIdentifier& identifier)
+                                                            const InputDeviceIdentifier& identifier,
+                                                            ftl::Flags<InputDeviceClass> classes)
             REQUIRES(mLock);
 
     // With each iteration of the loop, InputReader reads and processes one incoming message from
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index fd8224a..4d08f19 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -79,19 +79,17 @@
             if (id) {
                 outState->rawPointerData.canceledIdBits.markBit(id.value());
             }
-            if (DEBUG_POINTERS) {
-                ALOGI("Stop processing slot %zu for it received a palm event from device %s",
-                      inIndex, getDeviceName().c_str());
-            }
+            ALOGI_IF(DEBUG_POINTERS,
+                     "Stop processing slot %zu for it received a palm event from device %s",
+                     inIndex, getDeviceName().c_str());
             continue;
         }
 
         if (outCount >= MAX_POINTERS) {
-            if (DEBUG_POINTERS) {
-                ALOGD("MultiTouch device %s emitted more than maximum of %zu pointers; "
-                      "ignoring the rest.",
-                      getDeviceName().c_str(), MAX_POINTERS);
-            }
+            ALOGD_IF(DEBUG_POINTERS,
+                     "MultiTouch device %s emitted more than maximum of %zu pointers; ignoring the "
+                     "rest.",
+                     getDeviceName().c_str(), MAX_POINTERS);
             break; // too many fingers!
         }
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index 1f6600d..0d1d884 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -235,9 +235,8 @@
     // else calculate difference between previous and current MSC_TIMESTAMP
     if (mPrevMscTime == 0) {
         mHardwareTimestamp = evTime;
-        if (DEBUG_SENSOR_EVENT_DETAILS) {
-            ALOGD("Initialize hardware timestamp = %" PRId64, mHardwareTimestamp);
-        }
+        ALOGD_IF(DEBUG_SENSOR_EVENT_DETAILS, "Initialize hardware timestamp = %" PRId64,
+                 mHardwareTimestamp);
     } else {
         // Calculate the difference between current msc_timestamp and
         // previous msc_timestamp, including when msc_timestamp wraps around.
@@ -330,11 +329,10 @@
 bool SensorInputMapper::enableSensor(InputDeviceSensorType sensorType,
                                      std::chrono::microseconds samplingPeriod,
                                      std::chrono::microseconds maxBatchReportLatency) {
-    if (DEBUG_SENSOR_EVENT_DETAILS) {
-        ALOGD("Enable Sensor %s samplingPeriod %lld maxBatchReportLatency %lld",
-              ftl::enum_string(sensorType).c_str(), samplingPeriod.count(),
-              maxBatchReportLatency.count());
-    }
+    ALOGD_IF(DEBUG_SENSOR_EVENT_DETAILS,
+             "Enable Sensor %s samplingPeriod %lld maxBatchReportLatency %lld",
+             ftl::enum_string(sensorType).c_str(), samplingPeriod.count(),
+             maxBatchReportLatency.count());
 
     if (!setSensorEnabled(sensorType, /*enabled=*/true)) {
         return false;
@@ -355,9 +353,7 @@
 }
 
 void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) {
-    if (DEBUG_SENSOR_EVENT_DETAILS) {
-        ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str());
-    }
+    ALOGD_IF(DEBUG_SENSOR_EVENT_DETAILS, "Disable Sensor %s", ftl::enum_string(sensorType).c_str());
 
     if (!setSensorEnabled(sensorType, /*enabled=*/false)) {
         return;
@@ -389,15 +385,12 @@
         }
 
         nsecs_t timestamp = mHasHardwareTimestamp ? mHardwareTimestamp : when;
-        if (DEBUG_SENSOR_EVENT_DETAILS) {
-            ALOGD("Sensor %s timestamp %" PRIu64 " values [%f %f %f]",
-                  ftl::enum_string(sensorType).c_str(), timestamp, values[0], values[1], values[2]);
-        }
+        ALOGD_IF(DEBUG_SENSOR_EVENT_DETAILS, "Sensor %s timestamp %" PRIu64 " values [%f %f %f]",
+                 ftl::enum_string(sensorType).c_str(), timestamp, values[0], values[1], values[2]);
         if (sensor.lastSampleTimeNs.has_value() &&
             timestamp - sensor.lastSampleTimeNs.value() < sensor.samplingPeriod.count()) {
-            if (DEBUG_SENSOR_EVENT_DETAILS) {
-                ALOGD("Sensor %s Skip a sample.", ftl::enum_string(sensorType).c_str());
-            }
+            ALOGD_IF(DEBUG_SENSOR_EVENT_DETAILS, "Sensor %s Skip a sample.",
+                     ftl::enum_string(sensorType).c_str());
         } else {
             // Convert to Android unit
             convertFromLinuxToAndroid(values, sensorType);
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index a3a48ef..264ef6f 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -43,10 +43,8 @@
 
 std::list<NotifyArgs> VibratorInputMapper::vibrate(const VibrationSequence& sequence,
                                                    ssize_t repeat, int32_t token) {
-    if (DEBUG_VIBRATOR) {
-        ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
-              sequence.toString().c_str(), repeat, token);
-    }
+    ALOGD_IF(DEBUG_VIBRATOR, "vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d",
+             getDeviceId(), sequence.toString().c_str(), repeat, token);
     std::list<NotifyArgs> out;
 
     mVibrating = true;
@@ -63,9 +61,7 @@
 }
 
 std::list<NotifyArgs> VibratorInputMapper::cancelVibrate(int32_t token) {
-    if (DEBUG_VIBRATOR) {
-        ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
-    }
+    ALOGD_IF(DEBUG_VIBRATOR, "cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
     std::list<NotifyArgs> out;
 
     if (mVibrating && mToken == token) {
@@ -95,9 +91,7 @@
 }
 
 std::list<NotifyArgs> VibratorInputMapper::nextStep() {
-    if (DEBUG_VIBRATOR) {
-        ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId());
-    }
+    ALOGD_IF(DEBUG_VIBRATOR, "nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId());
     std::list<NotifyArgs> out;
     mIndex += 1;
     if (size_t(mIndex) >= mSequence.pattern.size()) {
@@ -111,16 +105,11 @@
 
     const VibrationElement& element = mSequence.pattern[mIndex];
     if (element.isOn()) {
-        if (DEBUG_VIBRATOR) {
-            std::string description = element.toString();
-            ALOGD("nextStep: sending vibrate deviceId=%d, element=%s", getDeviceId(),
-                  description.c_str());
-        }
+        ALOGD_IF(DEBUG_VIBRATOR, "nextStep: sending vibrate deviceId=%d, element=%s", getDeviceId(),
+                 element.toString().c_str());
         getDeviceContext().vibrate(element);
     } else {
-        if (DEBUG_VIBRATOR) {
-            ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
-        }
+        ALOGD_IF(DEBUG_VIBRATOR, "nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
         getDeviceContext().cancelVibrate();
     }
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -128,17 +117,13 @@
             std::chrono::duration_cast<std::chrono::nanoseconds>(element.duration);
     mNextStepTime = now + duration.count();
     getContext()->requestTimeoutAtTime(mNextStepTime);
-    if (DEBUG_VIBRATOR) {
-        ALOGD("nextStep: scheduled timeout in %lldms", element.duration.count());
-    }
+    ALOGD_IF(DEBUG_VIBRATOR, "nextStep: scheduled timeout in %lldms", element.duration.count());
     return out;
 }
 
 NotifyVibratorStateArgs VibratorInputMapper::stopVibrating() {
     mVibrating = false;
-    if (DEBUG_VIBRATOR) {
-        ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
-    }
+    ALOGD_IF(DEBUG_VIBRATOR, "stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
     getDeviceContext().cancelVibrate();
 
     // Request InputReader to notify InputManagerService for vibration complete.
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 7cc4ff7..7e7d954 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -9924,6 +9924,9 @@
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
+        // Use current time as start time otherwise events may be dropped due to being stale.
+        mGestureStartTime = std::chrono::nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC));
+
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
         application->setDispatchingTimeout(100ms);
@@ -9941,82 +9944,81 @@
         mWindow->consumeFocusEvent(true);
     }
 
-    void notifyAndConsumeMotion(int32_t action, uint32_t source, ui::LogicalDisplayId displayId,
-                                nsecs_t eventTime) {
-        mDispatcher->notifyMotion(MotionArgsBuilder(action, source)
-                                          .displayId(displayId)
-                                          .eventTime(eventTime)
-                                          .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
-                                          .build());
+    NotifyMotionArgs notifyAndConsumeMotion(int32_t action, uint32_t source,
+                                            ui::LogicalDisplayId displayId,
+                                            std::chrono::nanoseconds timeDelay) {
+        const NotifyMotionArgs motionArgs =
+                MotionArgsBuilder(action, source)
+                        .displayId(displayId)
+                        .eventTime((mGestureStartTime + timeDelay).count())
+                        .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                        .build();
+        mDispatcher->notifyMotion(motionArgs);
         mWindow->consumeMotionEvent(WithMotionAction(action));
+        return motionArgs;
     }
 
 private:
     sp<FakeWindowHandle> mWindow;
+    std::chrono::nanoseconds mGestureStartTime;
 };
 
 TEST_F_WITH_FLAGS(
         InputDispatcherUserActivityPokeTests, MinPokeTimeObserved,
         REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
                                             rate_limit_user_activity_poke_in_dispatcher))) {
-    // Use current time otherwise events may be dropped due to being stale.
-    const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-
     mDispatcher->setMinTimeBetweenUserActivityPokes(50ms);
 
     // First event of type TOUCH. Should poke.
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(50));
+    NotifyMotionArgs motionArgs =
+            notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(50));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_TOUCH, ui::LogicalDisplayId::DEFAULT}});
 
     // 80ns > 50ns has passed since previous TOUCH event. Should poke.
-    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(130));
+    motionArgs =
+            notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(130));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_TOUCH, ui::LogicalDisplayId::DEFAULT}});
 
     // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event).
-    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
-                           ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(135));
+    motionArgs =
+            notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(135));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_OTHER, ui::LogicalDisplayId::DEFAULT}});
 
     // Within 50ns of previous TOUCH event. Should NOT poke.
     notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(140));
+                           std::chrono::milliseconds(140));
     mFakePolicy->assertUserActivityNotPoked();
 
     // Within 50ns of previous OTHER event. Should NOT poke.
     notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
-                           ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(150));
+                           ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(150));
     mFakePolicy->assertUserActivityNotPoked();
 
     // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke.
     // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source.
     notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(160));
+                           std::chrono::milliseconds(160));
     mFakePolicy->assertUserActivityNotPoked();
 
     // 65ns > 50ns has passed since previous OTHER event. Should poke.
-    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
-                           ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(200));
+    motionArgs =
+            notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(200));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_OTHER, ui::LogicalDisplayId::DEFAULT}});
 
     // 170ns > 50ns has passed since previous TOUCH event. Should poke.
-    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(300));
+    motionArgs =
+            notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
+                                   std::chrono::milliseconds(300));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_TOUCH, ui::LogicalDisplayId::DEFAULT}});
 
     // Assert that there's no more user activity poke event.
     mFakePolicy->assertUserActivityNotPoked();
@@ -10026,39 +10028,35 @@
         InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed,
         REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
                                             rate_limit_user_activity_poke_in_dispatcher))) {
-    // Use current time otherwise events may be dropped due to being stale.
-    const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(200));
+    NotifyMotionArgs motionArgs =
+            notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(200));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_TOUCH, ui::LogicalDisplayId::DEFAULT}});
 
     notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(280));
+                           std::chrono::milliseconds(280));
     mFakePolicy->assertUserActivityNotPoked();
 
-    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + milliseconds_to_nanoseconds(340));
+    motionArgs =
+            notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, std::chrono::milliseconds(340));
     mFakePolicy->assertUserActivityPoked(
-            {{currentTime + milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH,
-              ui::LogicalDisplayId::DEFAULT}});
+            {{motionArgs.eventTime, USER_ACTIVITY_EVENT_TOUCH, ui::LogicalDisplayId::DEFAULT}});
 }
 
 TEST_F_WITH_FLAGS(
         InputDispatcherUserActivityPokeTests, ZeroMinPokeTimeDisablesRateLimiting,
         REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
                                             rate_limit_user_activity_poke_in_dispatcher))) {
-    // Use current time otherwise events may be dropped due to being stale.
-    const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     mDispatcher->setMinTimeBetweenUserActivityPokes(0ms);
 
     notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + 20);
+                           std::chrono::milliseconds(20));
     mFakePolicy->assertUserActivityPoked();
 
     notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                           currentTime + 30);
+                           std::chrono::milliseconds(30));
     mFakePolicy->assertUserActivityPoked();
 }
 
@@ -15187,4 +15185,102 @@
 
 INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool());
 
+class InputDispatcherConnectedDisplayTest : public InputDispatcherTest {
+    constexpr static int DENSITY_MEDIUM = 160;
+
+    const DisplayTopologyGraph
+            mTopology{.primaryDisplayId = DISPLAY_ID,
+                      .graph = {{DISPLAY_ID,
+                                 {{SECOND_DISPLAY_ID, DisplayTopologyPosition::TOP, 0.0f}}},
+                                {SECOND_DISPLAY_ID,
+                                 {{DISPLAY_ID, DisplayTopologyPosition::BOTTOM, 0.0f}}}},
+                      .displaysDensity = {{DISPLAY_ID, DENSITY_MEDIUM},
+                                          {SECOND_DISPLAY_ID, DENSITY_MEDIUM}}};
+
+protected:
+    sp<FakeWindowHandle> mWindow;
+
+    void SetUp() override {
+        InputDispatcherTest::SetUp();
+        mDispatcher->setDisplayTopology(mTopology);
+        mWindow = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
+                                             "Window", DISPLAY_ID);
+        mWindow->setFrame({0, 0, 100, 100});
+
+        gui::DisplayInfo displayInfo1;
+        displayInfo1.displayId = DISPLAY_ID;
+
+        ui::Transform transform(ui::Transform::ROT_270, /*logicalDisplayWidth=*/500,
+                                /*logicalDisplayHeight=*/500);
+        gui::DisplayInfo displayInfo2;
+        displayInfo2.displayId = SECOND_DISPLAY_ID;
+        displayInfo2.transform = transform;
+
+        mDispatcher->onWindowInfosChanged(
+                {{*mWindow->getInfo()}, {displayInfo1, displayInfo2}, 0, 0});
+    }
+};
+
+TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseGesture) {
+    SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true);
+
+    // pointer-down
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .displayId(DISPLAY_ID)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+                                      .build());
+    mWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                    .displayId(DISPLAY_ID)
+                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                      WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+    // pointer-move
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .displayId(DISPLAY_ID)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+                                      .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                      WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+    // pointer-move with different display
+    // TODO (b/383092013): by default windows will not be topology aware and receive events as it
+    // was in the same display. This behaviour has not been implemented yet.
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .displayId(SECOND_DISPLAY_ID)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+                                      .build());
+    // events should be delivered with the second displayId and in corrosponding coordinate space
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                      WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 430)));
+
+    // pointer-up
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE, AINPUT_SOURCE_MOUSE)
+                    .displayId(SECOND_DISPLAY_ID)
+                    .buttonState(0)
+                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+                    .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                      WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 430)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .displayId(SECOND_DISPLAY_ID)
+                                      .buttonState(0)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+                                      .build());
+    mWindow->consumeMotionUp(SECOND_DISPLAY_ID);
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index d9a75a5..43d2378 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1405,6 +1405,68 @@
     ASSERT_EQ(mFakeEventHub->fakeReadKernelWakeup(3), false);
 }
 
+TEST_F(InputReaderTest, MergeableInputDevices) {
+    constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
+
+    // By default, all of the default-created eventhub devices will have the same identifier
+    // (implicitly vid 0, pid 0, etc.), which is why we expect them to be merged.
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "1st", InputDeviceClass::KEYBOARD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "2nd", InputDeviceClass::JOYSTICK, nullptr));
+
+    // The two devices will be merged to one input device as they have same identifier, and none are
+    // pointer devices.
+    ASSERT_EQ(1U, mFakePolicy->getInputDevices().size());
+}
+
+TEST_F(InputReaderTest, MergeableDevicesWithTouch) {
+    constexpr int32_t eventHubIds[3] = {END_RESERVED_ID, END_RESERVED_ID + 1, END_RESERVED_ID + 2};
+
+    // By default, all of the default-created eventhub devices will have the same identifier
+    // (implicitly vid 0, pid 0, etc.), which is why we expect them to be merged.
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "1st", InputDeviceClass::TOUCH_MT, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "2nd", InputDeviceClass::KEYBOARD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[2], "3rd", InputDeviceClass::GAMEPAD, nullptr));
+
+    // The three devices will be merged to one input device as they have same identifier, and only
+    // one is a pointer device.
+    ASSERT_EQ(1U, mFakePolicy->getInputDevices().size());
+}
+
+TEST_F(InputReaderTest, UnmergeableTouchDevices) {
+    SCOPED_FLAG_OVERRIDE(prevent_merging_input_pointer_devices, true);
+
+    constexpr int32_t eventHubIds[3] = {END_RESERVED_ID, END_RESERVED_ID + 1, END_RESERVED_ID + 2};
+
+    // By default, all of the default-created eventhub devices will have the same identifier
+    // (implicitly vid 0, pid 0, etc.), which is why they can potentially be merged.
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "1st", InputDeviceClass::TOUCH, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "2nd", InputDeviceClass::TOUCH_MT, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[2], "2nd", InputDeviceClass::CURSOR, nullptr));
+
+    // The three devices will not be merged, as they have same identifier, but are all pointer
+    // devices.
+    ASSERT_EQ(3U, mFakePolicy->getInputDevices().size());
+}
+
+TEST_F(InputReaderTest, MergeableMixedDevices) {
+    SCOPED_FLAG_OVERRIDE(prevent_merging_input_pointer_devices, true);
+
+    constexpr int32_t eventHubIds[4] = {END_RESERVED_ID, END_RESERVED_ID + 1, END_RESERVED_ID + 2,
+                                        END_RESERVED_ID + 3};
+
+    // By default, all of the default-created eventhub devices will have the same identifier
+    // (implicitly vid 0, pid 0, etc.), which is why they can potentially be merged.
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "1st", InputDeviceClass::TOUCH, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "2nd", InputDeviceClass::TOUCH_MT, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[2], "3rd", InputDeviceClass::DPAD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[3], "4th", InputDeviceClass::JOYSTICK, nullptr));
+
+    // Non-touch devices can be merged with one of the touch devices, as they have same identifier,
+    // but the two touch devices will not combine with each other. It is not specified which touch
+    // device the non-touch devices merge with.
+    ASSERT_EQ(2U, mFakePolicy->getInputDevices().size());
+}
+
 // --- InputReaderIntegrationTest ---
 
 // These tests create and interact with the InputReader only through its interface.
diff --git a/services/inputflinger/tests/InstrumentedInputReader.cpp b/services/inputflinger/tests/InstrumentedInputReader.cpp
index 110ca5f..53fc8a1 100644
--- a/services/inputflinger/tests/InstrumentedInputReader.cpp
+++ b/services/inputflinger/tests/InstrumentedInputReader.cpp
@@ -38,13 +38,14 @@
 }
 
 std::shared_ptr<InputDevice> InstrumentedInputReader::createDeviceLocked(
-        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) {
+        nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier,
+        ftl::Flags<InputDeviceClass> classes) REQUIRES(mLock) {
     if (!mNextDevices.empty()) {
         std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
         mNextDevices.pop();
         return device;
     }
-    return InputReader::createDeviceLocked(when, eventHubId, identifier);
+    return InputReader::createDeviceLocked(when, eventHubId, identifier, classes);
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h
index e9c7bb4..9abf30c 100644
--- a/services/inputflinger/tests/InstrumentedInputReader.h
+++ b/services/inputflinger/tests/InstrumentedInputReader.h
@@ -43,8 +43,9 @@
     using InputReader::loopOnce;
 
 protected:
-    virtual std::shared_ptr<InputDevice> createDeviceLocked(
-            nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier);
+    virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t eventHubId,
+                                                            const InputDeviceIdentifier& identifier,
+                                                            ftl::Flags<InputDeviceClass> classes);
 
     class FakeInputReaderContext : public ContextImpl {
     public:
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index dce5472..06d60ce 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -191,6 +191,9 @@
                 (ui::LogicalDisplayId displayId, const vec2& position), (override));
     MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
     MOCK_METHOD(void, notifyMouseCursorFadedOnTyping, (), (override));
+    MOCK_METHOD(std::optional<vec2>, filterPointerMotionForAccessibility,
+                (const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId),
+                (override));
 };
 
 class MockInputDevice : public InputDevice {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 1286a36..d02c3d9 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -1776,6 +1776,89 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
+TEST_F(PointerChoreographerTest, A11yPointerMotionFilterMouse) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    pc->setPosition(100, 200);
+    mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
+
+    EXPECT_CALL(mMockPolicy,
+                filterPointerMotionForAccessibility(testing::Eq(vec2{100, 200}),
+                                                    testing::Eq(vec2{10.f, 20.f}),
+                                                    testing::Eq(DISPLAY_ID)))
+            .Times(1)
+            .WillOnce(testing::Return(vec2{4, 13}));
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ui::LogicalDisplayId::INVALID)
+                    .build());
+
+    // Cursor position is decided by filtered delta, but pointer coord's relative values are kept.
+    pc->assertPosition(104, 213);
+    mTestListener.assertNotifyMotionWasCalled(AllOf(WithCoords(104, 213), WithDisplayId(DISPLAY_ID),
+                                                    WithCursorPosition(104, 213),
+                                                    WithRelativeMotion(10, 20)));
+}
+
+TEST_F(PointerChoreographerTest, A11yPointerMotionFilterTouchpad) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    pc->setPosition(100, 200);
+    mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
+
+    EXPECT_CALL(mMockPolicy,
+                filterPointerMotionForAccessibility(testing::Eq(vec2{100, 200}),
+                                                    testing::Eq(vec2{10.f, 20.f}),
+                                                    testing::Eq(DISPLAY_ID)))
+            .Times(1)
+            .WillOnce(testing::Return(vec2{4, 13}));
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ui::LogicalDisplayId::INVALID)
+                    .build());
+
+    // Cursor position is decided by filtered delta, but pointer coord's relative values are kept.
+    pc->assertPosition(104, 213);
+    mTestListener.assertNotifyMotionWasCalled(AllOf(WithCoords(104, 213), WithDisplayId(DISPLAY_ID),
+                                                    WithCursorPosition(104, 213),
+                                                    WithRelativeMotion(10, 20)));
+}
+
+TEST_F(PointerChoreographerTest, A11yPointerMotionFilterNotFilterTouch) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
+
+    EXPECT_CALL(mMockPolicy, filterPointerMotionForAccessibility).Times(0);
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+}
+
 using SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam =
         std::tuple<std::string_view /*name*/, uint32_t /*source*/, ControllerType, PointerBuilder,
                    std::function<void(PointerChoreographer&)>, int32_t /*action*/>;
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index 880df08..f38cf5a 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -22,6 +22,7 @@
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V3-ndk",
         "android.hardware.common-V2-ndk",
+        "framework-permission-aidl-cpp",
         "libsensor",
         "libfakeservicemanager",
         "libcutils",
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 88ff370..9aa1ffa 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -93,7 +93,6 @@
         "iinputflinger_aidl_lib_static",
         "libaidlcommonsupport",
         "libcompositionengine",
-        "libframetimeline",
         "libgui_aidl_static",
         "libperfetto_client_experimental",
         "librenderengine",
@@ -208,6 +207,7 @@
         "DisplayDevice.cpp",
         "Effects/Daltonizer.cpp",
         "FpsReporter.cpp",
+        "FrameTimeline/FrameTimeline.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "FrontEnd/LayerCreationArgs.cpp",
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 77bf145..6088e25 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -110,8 +110,8 @@
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this),
                            "MirrorRoot-" + std::to_string(displayId), 0 /* flags */,
                            gui::LayerMetadata());
-    std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
-    status_t status = mFlinger->mirrorDisplay(*id, args, *outResult);
+    const DisplayId id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
+    status_t status = mFlinger->mirrorDisplay(id, args, *outResult);
     return binderStatusFromStatusT(status);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index e876693..780758e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -19,6 +19,7 @@
 #include <optional>
 #include <ostream>
 #include <unordered_set>
+#include "aidl/android/hardware/graphics/composer3/Composition.h"
 #include "ui/LayerStack.h"
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -36,10 +37,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-namespace aidl::android::hardware::graphics::composer3 {
-enum class Composition;
-}
-
 namespace android {
 
 class Fence;
@@ -182,10 +179,27 @@
     // Whether the layer should be rendered with rounded corners.
     virtual bool hasRoundedCorners() const = 0;
     virtual void setWasClientComposed(const sp<Fence>&) {}
-    virtual void setHwcCompositionType(
-            aidl::android::hardware::graphics::composer3::Composition) = 0;
-    virtual aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
-            const = 0;
+
+    // These fields are all copied from the last written HWC state.
+    // This state is only used for debugging purposes.
+    struct HwcLayerDebugState {
+        aidl::android::hardware::graphics::composer3::Composition lastCompositionType =
+                aidl::android::hardware::graphics::composer3::Composition::INVALID;
+        // Corresponds to passing an alpha of 0 to HWC2::Layer::setPlaneAlpha.
+        bool wasSkipped = false;
+
+        // Indicates whether the compositionengine::OutputLayer had properties overwritten.
+        // Not directly passed to HWC.
+        bool wasOverridden = false;
+
+        // Corresponds to the GraphicBuffer ID of the buffer passed to HWC2::Layer::setBuffer.
+        // This buffer corresponds to a CachedSet that the LayerFE was flattened to.
+        uint64_t overrideBufferId = 0;
+    };
+
+    // Used for debugging purposes, e.g. perfetto tracing, dumpsys.
+    virtual void setLastHwcState(const LayerFE::HwcLayerDebugState &hwcState) = 0;
+    virtual const HwcLayerDebugState &getLastHwcState() const = 0;
 
     virtual const gui::LayerMetadata* getMetadata() const = 0;
     virtual const gui::LayerMetadata* getRelativeMetadata() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 0063eee..a1434f2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -104,7 +104,7 @@
     void detectDisallowedCompositionTypeChange(
             aidl::android::hardware::graphics::composer3::Composition from,
             aidl::android::hardware::graphics::composer3::Composition to) const;
-    bool isClientCompositionForced(bool isPeekingThrough) const;
+    bool isClientCompositionForced(bool isPeekingThrough, bool isCached) const;
     void updateLuts(const LayerFECompositionState&,
                     const std::optional<std::vector<std::optional<LutProperties>>>& properties);
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 7744b8b..d2a5a20 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -59,9 +59,9 @@
     MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*());
     MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*());
     MOCK_METHOD0(onPictureProfileCommitted, void());
-    MOCK_METHOD(void, setHwcCompositionType,
-                (aidl::android::hardware::graphics::composer3::Composition), (override));
-    MOCK_METHOD(aidl::android::hardware::graphics::composer3::Composition, getHwcCompositionType,
+    MOCK_METHOD(void, setLastHwcState,
+                (const HwcLayerDebugState&), (override));
+    MOCK_METHOD(const HwcLayerDebugState&, getLastHwcState,
                 (), (const, override));
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 9b66f01..d89b52d 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -502,6 +502,15 @@
 
     editState().hwc->stateOverridden = isOverridden;
     editState().hwc->layerSkipped = skipLayer;
+
+
+    // Save the final HWC state for debugging purposes, e.g. perfetto tracing, dumpsys.
+    getLayerFE().setLastHwcState({.lastCompositionType = editState().hwc->hwcCompositionType,
+                                  .wasSkipped = skipLayer,
+                                  .wasOverridden = isOverridden,
+                                  .overrideBufferId = editState().overrideInfo.buffer
+                                          ? editState().overrideInfo.buffer.get()->getId()
+                                          : 0});
 }
 
 void OutputLayer::writeOutputDependentGeometryStateToHWC(HWC2::Layer* hwcLayer,
@@ -856,7 +865,8 @@
                                             bool isPeekingThrough, bool skipLayer) {
     auto& outputDependentState = editState();
 
-    if (isClientCompositionForced(isPeekingThrough)) {
+    bool isCached = !skipLayer && outputDependentState.overrideInfo.buffer;
+    if (isClientCompositionForced(isPeekingThrough, isCached)) {
         // If we are forcing client composition, we need to tell the HWC
         requestedCompositionType = Composition::CLIENT;
     }
@@ -867,7 +877,6 @@
     if (outputDependentState.hwc->hwcCompositionType != requestedCompositionType ||
         (outputDependentState.hwc->layerSkipped && !skipLayer)) {
         outputDependentState.hwc->hwcCompositionType = requestedCompositionType;
-        getLayerFE().setHwcCompositionType(requestedCompositionType);
 
         if (auto error = hwcLayer->setCompositionType(requestedCompositionType);
             error != hal::Error::NONE) {
@@ -947,9 +956,12 @@
     }
 }
 
-bool OutputLayer::isClientCompositionForced(bool isPeekingThrough) const {
+bool OutputLayer::isClientCompositionForced(bool isPeekingThrough, bool isCached) const {
+    // If this layer was flattened into a CachedSet then it is not necessary for
+    // the GPU to compose it.
+    bool requiresClientDrawnRoundedCorners = !isCached && getLayerFE().hasRoundedCorners();
     return getState().forceClientComposition ||
-            (!isPeekingThrough && getLayerFE().hasRoundedCorners());
+            (!isPeekingThrough && requiresClientDrawnRoundedCorners);
 }
 
 void OutputLayer::applyDeviceCompositionTypeChange(Composition compositionType) {
@@ -965,7 +977,13 @@
     }
 
     hwcState.hwcCompositionType = compositionType;
-    getLayerFE().setHwcCompositionType(compositionType);
+
+    getLayerFE().setLastHwcState({.lastCompositionType = hwcState.hwcCompositionType,
+                                  .wasSkipped = hwcState.layerSkipped,
+                                  .wasOverridden = hwcState.stateOverridden,
+                                  .overrideBufferId = state.overrideInfo.buffer
+                                          ? state.overrideInfo.buffer.get()->getId()
+                                          : 0});
 }
 
 void OutputLayer::prepareForDeviceLayerRequests() {
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
index ec3ec52..2e9dc1e 100644
--- a/services/surfaceflinger/Display/DisplayModeRequest.h
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -26,7 +26,8 @@
 struct DisplayModeRequest {
     scheduler::FrameRateMode mode;
 
-    // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
+    // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE for a change in refresh rate
+    // or render rate. Ignored for resolution changes, which always emit the event.
     bool emitEvent = false;
 
     // Whether to force the request to be applied, even if the mode is unchanged.
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index c743ea2..e8b09b0 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -223,9 +223,7 @@
     mFlags = flags;
 }
 
-void DisplayDevice::setDisplaySize(int width, int height) {
-    LOG_FATAL_IF(!isVirtual(), "Changing the display size is supported only for virtual displays.");
-    const auto size = ui::Size(width, height);
+void DisplayDevice::setDisplaySize(ui::Size size) {
     mCompositionDisplay->setDisplaySize(size);
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setViewport(size);
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index af2b48f..b5a543a 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -98,7 +98,7 @@
     ui::Size getSize() const { return {getWidth(), getHeight()}; }
 
     void setLayerFilter(ui::LayerFilter);
-    void setDisplaySize(int width, int height);
+    void setDisplaySize(ui::Size);
     void setProjection(ui::Rotation orientation, Rect viewport, Rect frame);
     void stageBrightness(float brightness) REQUIRES(kMainThreadContext);
     void persistBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
@@ -260,6 +260,7 @@
     struct Physical {
         PhysicalDisplayId id;
         hardware::graphics::composer::hal::HWDisplayId hwcDisplayId;
+        uint8_t port;
         DisplayModePtr activeMode;
 
         bool operator==(const Physical& other) const {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index c47943f..db41b9b 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -225,7 +225,11 @@
 }
 
 void HWComposer::allocatePhysicalDisplay(hal::HWDisplayId hwcDisplayId, PhysicalDisplayId displayId,
-                                         std::optional<ui::Size> physicalSize) {
+                                         uint8_t port, std::optional<ui::Size> physicalSize) {
+    LOG_ALWAYS_FATAL_IF(!mActivePorts.try_emplace(port).second,
+                        "Cannot attach display %" PRIu64 " to an already active port %" PRIu8 ".",
+                        hwcDisplayId, port);
+
     mPhysicalDisplayIdMap[hwcDisplayId] = displayId;
 
     if (!mPrimaryHwcDisplayId) {
@@ -239,6 +243,7 @@
     newDisplay->setConnected(true);
     newDisplay->setPhysicalSizeInMm(physicalSize);
     displayData.hwcDisplay = std::move(newDisplay);
+    displayData.port = port;
 }
 
 int32_t HWComposer::getAttribute(hal::HWDisplayId hwcDisplayId, hal::HWConfigId configId,
@@ -758,6 +763,9 @@
     const auto hwcDisplayId = displayData.hwcDisplay->getId();
 
     mPhysicalDisplayIdMap.erase(hwcDisplayId);
+    if (const auto port = displayData.port) {
+        mActivePorts.erase(port.value());
+    }
     mDisplayData.erase(displayId);
 
     // Reset the primary display ID if we're disconnecting it.
@@ -1123,8 +1131,15 @@
     return {};
 }
 
-bool HWComposer::shouldIgnoreHotplugConnect(hal::HWDisplayId hwcDisplayId,
+bool HWComposer::shouldIgnoreHotplugConnect(hal::HWDisplayId hwcDisplayId, uint8_t port,
                                             bool hasDisplayIdentificationData) const {
+    if (mActivePorts.contains(port)) {
+        ALOGE("Ignoring connection of display %" PRIu64 ". Port %" PRIu8
+              " is already in active use.",
+              hwcDisplayId, port);
+        return true;
+    }
+
     if (mHasMultiDisplaySupport && !hasDisplayIdentificationData) {
         ALOGE("Ignoring connection of display %" PRIu64 " without identification data",
               hwcDisplayId);
@@ -1170,7 +1185,7 @@
                   mHasMultiDisplaySupport ? "generalized" : "legacy");
         }
 
-        if (shouldIgnoreHotplugConnect(hwcDisplayId, hasDisplayIdentificationData)) {
+        if (shouldIgnoreHotplugConnect(hwcDisplayId, port, hasDisplayIdentificationData)) {
             return {};
         }
 
@@ -1202,7 +1217,7 @@
         if (info->preferredDetailedTimingDescriptor) {
             size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
         }
-        allocatePhysicalDisplay(hwcDisplayId, info->id, size);
+        allocatePhysicalDisplay(hwcDisplayId, info->id, info->port, size);
     }
     return info;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index d60f6ff..2c0aa3d 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -28,6 +28,7 @@
 #include <ftl/expected.h>
 #include <ftl/future.h>
 #include <ui/DisplayIdentification.h>
+#include <ui/DisplayMap.h>
 #include <ui/FenceTime.h>
 #include <ui/PictureProfileHandle.h>
 
@@ -144,7 +145,7 @@
     // supported by the HWC can be queried in advance, but allocation may fail for other reasons.
     virtual bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) = 0;
 
-    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId, uint8_t port,
                                          std::optional<ui::Size> physicalSize) = 0;
 
     // Attempts to create a new layer on this display
@@ -362,7 +363,7 @@
     bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) override;
 
     // Called from SurfaceFlinger, when the state for a new physical display needs to be recreated.
-    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId, uint8_t port,
                                  std::optional<ui::Size> physicalSize) override;
 
     // Attempts to create a new layer on this display
@@ -525,6 +526,7 @@
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
+        std::optional<uint8_t> port; // Set on hotplug for physical displays
 
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         nsecs_t lastPresentTimestamp = 0;
@@ -542,7 +544,8 @@
 
     std::optional<DisplayIdentificationInfo> onHotplugConnect(hal::HWDisplayId);
     std::optional<DisplayIdentificationInfo> onHotplugDisconnect(hal::HWDisplayId);
-    bool shouldIgnoreHotplugConnect(hal::HWDisplayId, bool hasDisplayIdentificationData) const;
+    bool shouldIgnoreHotplugConnect(hal::HWDisplayId, uint8_t port,
+                                    bool hasDisplayIdentificationData) const;
 
     aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi
     getEstimatedDotsPerInchFromSize(uint64_t hwcDisplayId, const HWCDisplayMode& hwcMode) const;
@@ -564,6 +567,7 @@
     void loadHdrConversionCapabilities();
 
     std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
+    ui::PhysicalDisplaySet<uint8_t> mActivePorts;
 
     std::unique_ptr<android::Hwc2::Composer> mComposer;
     std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
deleted file mode 100644
index 8e28cc3..0000000
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-    default_team: "trendy_team_android_core_graphics_stack",
-}
-
-cc_library_static {
-    name: "libframetimeline",
-    defaults: ["surfaceflinger_defaults"],
-    srcs: [
-        "FrameTimeline.cpp",
-    ],
-    header_libs: [
-        "libscheduler_headers",
-    ],
-    shared_libs: [
-        "android.hardware.graphics.composer@2.4",
-        "libbase",
-        "libcutils",
-        "liblog",
-        "libgui",
-        "libtimestats",
-        "libui",
-        "libutils",
-    ],
-    static_libs: [
-        "libperfetto_client_experimental",
-        "libsurfaceflinger_common",
-    ],
-    export_include_dirs: ["."],
-}
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 523ef7b..1f0d5d0 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -305,7 +305,11 @@
             out << rootId << ",";
         }
     }
-    out << "] " << obj.name << "\n    " << (obj.isVisible ? "visible" : "invisible")
+    out << "] ";
+    if (obj.isSecure) {
+        out << "(Secure) ";
+    }
+    out << obj.name << "\n    " << (obj.isVisible ? "visible" : "invisible")
         << " reason=" << obj.getIsVisibleReason();
 
     if (!obj.geomLayerBounds.isEmpty()) {
@@ -403,11 +407,8 @@
     if (forceUpdate || requested.what & layer_state_t::eSidebandStreamChanged) {
         sidebandStream = requested.sidebandStream;
     }
-    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged ||
-        requested.what & layer_state_t::eClientDrawnShadowsChanged) {
-        shadowSettings.length =
-                requested.clientDrawnShadowRadius > 0 ? 0.f : requested.shadowRadius;
-        shadowSettings.clientDrawnLength = requested.clientDrawnShadowRadius;
+    if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
+        shadowSettings.length = requested.shadowRadius;
     }
 
     if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
@@ -537,16 +538,17 @@
     }
 }
 
-char LayerSnapshot::classifyCompositionForDebug(Composition compositionType) const {
+char LayerSnapshot::classifyCompositionForDebug(
+        const compositionengine::LayerFE::HwcLayerDebugState& hwcState) const {
     if (!isVisible) {
         return '.';
     }
 
-    switch (compositionType) {
+    switch (hwcState.lastCompositionType) {
         case Composition::INVALID:
             return 'i';
         case Composition::SOLID_COLOR:
-            return 'c';
+            return 'e';
         case Composition::CURSOR:
             return 'u';
         case Composition::SIDEBAND:
@@ -554,28 +556,28 @@
         case Composition::DISPLAY_DECORATION:
             return 'a';
         case Composition::REFRESH_RATE_INDICATOR:
-            return 'r';
+            return 'f';
         case Composition::CLIENT:
         case Composition::DEVICE:
             break;
     }
 
     char code = '.'; // Default to invisible
-    if (hasBufferOrSidebandStream()) {
-        code = 'b';
-    } else if (fillsColor()) {
-        code = 'c'; // Solid color
-    } else if (hasBlur()) {
+    if (hasBlur()) {
         code = 'l'; // Blur
     } else if (hasProtectedContent) {
         code = 'p'; // Protected content
-    } else if (drawShadows()) {
-        code = 's'; // Shadow
     } else if (roundedCorner.hasRoundedCorners()) {
         code = 'r'; // Rounded corners
+    } else if (drawShadows()) {
+        code = 's'; // Shadow
+    } else if (fillsColor()) {
+        code = 'c'; // Solid color
+    } else if (hasBufferOrSidebandStream()) {
+        code = 'b';
     }
 
-    if (compositionType == Composition::CLIENT) {
+    if (hwcState.lastCompositionType == Composition::CLIENT) {
         return static_cast<char>(std::toupper(code));
     } else {
         return code;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 04b9f3b..69120bd 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -24,6 +24,7 @@
 #include "RequestedLayerState.h"
 #include "Scheduler/LayerInfo.h"
 #include "android-base/stringprintf.h"
+#include "compositionengine/LayerFE.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -163,7 +164,7 @@
     // Returns a char summarizing the composition request
     // This function tries to maintain parity with planner::Plan chars.
     char classifyCompositionForDebug(
-            aidl::android::hardware::graphics::composer3::Composition compositionType) const;
+            const compositionengine::LayerFE::HwcLayerDebugState& hwcState) const;
 };
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 1d53e71..58c235e 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -108,7 +108,6 @@
     surfaceDamageRegion = Region::INVALID_REGION;
     cornerRadius = 0.0f;
     clientDrawnCornerRadius = 0.0f;
-    clientDrawnShadowRadius = 0.0f;
     backgroundBlurRadius = 0;
     api = -1;
     hasColorTransform = false;
@@ -355,11 +354,6 @@
         clientDrawnCornerRadius = clientState.clientDrawnCornerRadius;
         changes |= RequestedLayerState::Changes::Geometry;
     }
-
-    if (clientState.what & layer_state_t::eClientDrawnShadowsChanged) {
-        clientDrawnShadowRadius = clientState.clientDrawnShadowRadius;
-        changes |= RequestedLayerState::Changes::Geometry;
-    }
 }
 
 ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const {
@@ -646,7 +640,6 @@
             layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged |
             layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eClientDrawnCornerRadiusChanged |
-            layer_state_t::eClientDrawnShadowsChanged |
             layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged |
             layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
             layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
diff --git a/services/surfaceflinger/Jank/JankTracker.cpp b/services/surfaceflinger/Jank/JankTracker.cpp
index 8e0e084..5e6267d 100644
--- a/services/surfaceflinger/Jank/JankTracker.cpp
+++ b/services/surfaceflinger/Jank/JankTracker.cpp
@@ -88,7 +88,8 @@
 }
 
 void JankTracker::addJankListenerLocked(int32_t layerId, sp<IBinder> listener) {
-    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+    auto range = mJankListeners.equal_range(layerId);
+    for (auto it = range.first; it != range.second; it++) {
         if (it->second.mListener == listener) {
             // Undo the duplicate increment in addJankListener.
             sListenerCount--;
@@ -106,7 +107,8 @@
     std::vector<sp<IBinder>> toSend;
 
     mLock.lock();
-    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end();) {
+    auto range = mJankListeners.equal_range(layerId);
+    for (auto it = range.first; it != range.second;) {
         if (!jankData.empty()) {
             toSend.emplace_back(it->second.mListener);
         }
@@ -133,7 +135,8 @@
 
 void JankTracker::markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener,
                                                    int64_t afterVysnc) {
-    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+    auto range = mJankListeners.equal_range(layerId);
+    for (auto it = range.first; it != range.second; it++) {
         if (it->second.mListener == listener) {
             it->second.mRemoveAfter = std::max(static_cast<int64_t>(0), afterVysnc);
             return;
@@ -156,7 +159,8 @@
 
 void JankTracker::dropJankListener(int32_t layerId, sp<IBinder> listener) {
     const std::lock_guard<std::mutex> _l(mLock);
-    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+    auto range = mJankListeners.equal_range(layerId);
+    for (auto it = range.first; it != range.second; it++) {
         if (it->second.mListener == listener) {
             mJankListeners.erase(it);
             sListenerCount--;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 195461f..e1bba44 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -64,7 +64,7 @@
 
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWComposer.h"
-#include "FrameTimeline.h"
+#include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 725a782..b619268 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -428,13 +428,12 @@
     return mReleaseFencePromiseStatus;
 }
 
-void LayerFE::setHwcCompositionType(
-        aidl::android::hardware::graphics::composer3::Composition type) {
-    mLastHwcCompositionType = type;
+void LayerFE::setLastHwcState(const LayerFE::HwcLayerDebugState &state) {
+    mLastHwcState = state;
 }
 
-aidl::android::hardware::graphics::composer3::Composition LayerFE::getHwcCompositionType() const {
-    return mLastHwcCompositionType;
-}
+const LayerFE::HwcLayerDebugState& LayerFE::getLastHwcState() const {
+    return mLastHwcState;
+};
 
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 64ec278..a537456 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -59,9 +59,10 @@
     void setReleaseFence(const FenceResult& releaseFence) override;
     LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
     void onPictureProfileCommitted() override;
-    void setHwcCompositionType(aidl::android::hardware::graphics::composer3::Composition) override;
-    aidl::android::hardware::graphics::composer3::Composition getHwcCompositionType()
-            const override;
+
+    // Used for debugging purposes, e.g. perfetto tracing, dumpsys.
+    void setLastHwcState(const HwcLayerDebugState &state) override;
+    const HwcLayerDebugState &getLastHwcState() const override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -93,8 +94,7 @@
     std::string mName;
     std::promise<FenceResult> mReleaseFence;
     ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
-    aidl::android::hardware::graphics::composer3::Composition mLastHwcCompositionType =
-            aidl::android::hardware::graphics::composer3::Composition::INVALID;
+    HwcLayerDebugState mLastHwcState;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index fa0ecee..13edd16 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -12,6 +12,5 @@
 ramindani@google.com
 rnlee@google.com
 sallyqi@google.com
-scroggo@google.com
 vishnun@google.com
 xwxw@google.com
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 1c4a11a..514adac 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -356,12 +356,10 @@
     screenshotArgs.seamlessTransition = false;
 
     std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
-    auto displayState =
-            mFlinger.getSnapshotsFromMainThread(screenshotArgs, getLayerSnapshotsFn, layers);
-    FenceResult fenceResult =
-            mFlinger.captureScreenshot(screenshotArgs, buffer, kRegionSampling, kGrayscale,
-                                       kIsProtected, nullptr, displayState, layers)
-                    .get();
+    mFlinger.getSnapshotsFromMainThread(screenshotArgs, getLayerSnapshotsFn, layers);
+    FenceResult fenceResult = mFlinger.captureScreenshot(screenshotArgs, buffer, kRegionSampling,
+                                                         kGrayscale, kIsProtected, nullptr, layers)
+                                      .get();
     if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
     }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 0efc396..c37b965 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -45,7 +45,7 @@
 #include <common/FlagManager.h>
 #include <scheduler/FrameRateMode.h>
 #include <scheduler/VsyncConfig.h>
-#include "FrameTimeline.h"
+#include "FrameTimeline/FrameTimeline.h"
 #include "VSyncDispatch.h"
 
 #include "EventThread.h"
@@ -86,36 +86,43 @@
 
 std::string toString(const DisplayEventReceiver::Event& event) {
     switch (event.header.type) {
-        case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+        case DisplayEventType::DISPLAY_EVENT_HOTPLUG:
             return StringPrintf("Hotplug{displayId=%s, %s}",
                                 to_string(event.header.displayId).c_str(),
                                 event.hotplug.connected ? "connected" : "disconnected");
-        case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+        case DisplayEventType::DISPLAY_EVENT_VSYNC:
             return StringPrintf("VSync{displayId=%s, count=%u, expectedPresentationTime=%" PRId64
                                 "}",
                                 to_string(event.header.displayId).c_str(), event.vsync.count,
                                 event.vsync.vsyncData.preferredExpectedPresentationTime());
-        case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
+        case DisplayEventType::DISPLAY_EVENT_MODE_CHANGE:
             return StringPrintf("ModeChanged{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(), event.modeChange.modeId);
-        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+        case DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
             return StringPrintf("HdcpLevelsChange{displayId=%s, connectedLevel=%d, maxLevel=%d}",
                                 to_string(event.header.displayId).c_str(),
                                 event.hdcpLevelsChange.connectedLevel,
                                 event.hdcpLevelsChange.maxLevel);
-        case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+        case DisplayEventType::DISPLAY_EVENT_MODE_REJECTION:
             return StringPrintf("ModeRejected{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(),
                                 event.modeRejection.modeId);
-        default:
-            return "Event{}";
+        case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+            return StringPrintf("FrameRateOverride{displayId=%s, frameRateHz=%f}",
+                                to_string(event.header.displayId).c_str(),
+                                event.frameRateOverride.frameRateHz);
+        case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
+            return StringPrintf("FrameRateOverrideFlush{displayId=%s}",
+                                to_string(event.header.displayId).c_str());
+        case DisplayEventType::DISPLAY_EVENT_NULL:
+            return "NULL";
     }
 }
 
 DisplayEventReceiver::Event makeHotplug(PhysicalDisplayId displayId, nsecs_t timestamp,
                                         bool connected) {
     DisplayEventReceiver::Event event;
-    event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, displayId, timestamp};
+    event.header = {DisplayEventType::DISPLAY_EVENT_HOTPLUG, displayId, timestamp};
     event.hotplug.connected = connected;
     return event;
 }
@@ -123,7 +130,7 @@
 DisplayEventReceiver::Event makeHotplugError(nsecs_t timestamp, int32_t connectionError) {
     DisplayEventReceiver::Event event;
     PhysicalDisplayId unusedDisplayId;
-    event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp};
+    event.header = {DisplayEventType::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp};
     event.hotplug.connected = false;
     event.hotplug.connectionError = connectionError;
     return event;
@@ -133,7 +140,7 @@
                                       uint32_t count, nsecs_t expectedPresentationTime,
                                       nsecs_t deadlineTimestamp) {
     DisplayEventReceiver::Event event;
-    event.header = {DisplayEventReceiver::DISPLAY_EVENT_VSYNC, displayId, timestamp};
+    event.header = {DisplayEventType::DISPLAY_EVENT_VSYNC, displayId, timestamp};
     event.vsync.count = count;
     event.vsync.vsyncData.preferredFrameTimelineIndex = 0;
     // Temporarily store the current vsync information in frameTimelines[0], marked as
@@ -148,7 +155,7 @@
 
 DisplayEventReceiver::Event makeModeChanged(const scheduler::FrameRateMode& mode) {
     DisplayEventReceiver::Event event;
-    event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE,
+    event.header = {DisplayEventType::DISPLAY_EVENT_MODE_CHANGE,
                     mode.modePtr->getPhysicalDisplayId(), systemTime()};
     event.modeChange.modeId = ftl::to_underlying(mode.modePtr->getId());
     event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs();
@@ -160,7 +167,7 @@
     return DisplayEventReceiver::Event{
             .header =
                     DisplayEventReceiver::Event::Header{
-                            .type = DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
+                            .type = DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
                             .displayId = displayId,
                             .timestamp = systemTime(),
                     },
@@ -171,7 +178,7 @@
 DisplayEventReceiver::Event makeFrameRateOverrideFlushEvent(PhysicalDisplayId displayId) {
     return DisplayEventReceiver::Event{
             .header = DisplayEventReceiver::Event::Header{
-                    .type = DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH,
+                    .type = DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH,
                     .displayId = displayId,
                     .timestamp = systemTime(),
             }};
@@ -182,7 +189,7 @@
     return DisplayEventReceiver::Event{
             .header =
                     DisplayEventReceiver::Event::Header{
-                            .type = DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE,
+                            .type = DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE,
                             .displayId = displayId,
                             .timestamp = systemTime(),
                     },
@@ -195,7 +202,7 @@
     return DisplayEventReceiver::Event{
             .header =
                     DisplayEventReceiver::Event::Header{
-                            .type = DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION,
+                            .type = DisplayEventType::DISPLAY_EVENT_MODE_REJECTION,
                             .displayId = displayId,
                             .timestamp = systemTime(),
                     },
@@ -263,10 +270,10 @@
         return size < 0 ? status_t(size) : status_t(NO_ERROR);
     };
 
-    if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE ||
-        event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH) {
+    if (event.header.type == DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE ||
+        event.header.type == DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH) {
         mPendingEvents.emplace_back(event);
-        if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE) {
+        if (event.header.type == DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE) {
             return status_t(NO_ERROR);
         }
 
@@ -524,7 +531,7 @@
             event = mPendingEvents.front();
             mPendingEvents.pop_front();
 
-            if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
+            if (event->header.type == DisplayEventType::DISPLAY_EVENT_HOTPLUG) {
                 if (event->hotplug.connectionError == 0) {
                     if (event->hotplug.connected && !mVSyncState) {
                         mVSyncState.emplace();
@@ -636,18 +643,21 @@
     };
 
     switch (event.header.type) {
-        case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+        case DisplayEventType::DISPLAY_EVENT_HOTPLUG:
             return true;
 
-        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+        case DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
             return true;
 
-        case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
+        case DisplayEventType::DISPLAY_EVENT_MODE_CHANGE: {
             return connection->mEventRegistration.test(
                     gui::ISurfaceComposer::EventRegistration::modeChanged);
         }
 
-        case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+        case DisplayEventType::DISPLAY_EVENT_MODE_REJECTION:
+            return true;
+
+        case DisplayEventType::DISPLAY_EVENT_VSYNC:
             switch (connection->vsyncRequest) {
                 case VSyncRequest::None:
                     return false;
@@ -673,13 +683,12 @@
                     return event.vsync.count % vsyncPeriod(connection->vsyncRequest) == 0;
             }
 
-        case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+        case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
             [[fallthrough]];
-        case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
+        case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
             return connection->mEventRegistration.test(
                     gui::ISurfaceComposer::EventRegistration::frameRateOverride);
-
-        default:
+        case DisplayEventType::DISPLAY_EVENT_NULL:
             return false;
     }
 }
@@ -758,7 +767,7 @@
     ftl::SmallVector<uid_t, 10> uidsPostedQueuedBuffers;
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
-        if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+        if (event.header.type == DisplayEventType::DISPLAY_EVENT_VSYNC) {
             const Period frameInterval = mCallback.getVsyncPeriod(consumer->mOwnerUid);
             copy.vsync.vsyncData.frameInterval = frameInterval.ns();
             generateFrameTimeline(copy.vsync.vsyncData, frameInterval.ns(), copy.header.timestamp,
@@ -793,7 +802,7 @@
     for (auto uid : uidsPostedQueuedBuffers) {
         mBufferStuffedUids.erase(uid);
     }
-    if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
+    if (event.header.type == DisplayEventType::DISPLAY_EVENT_VSYNC &&
         FlagManager::getInstance().vrr_config()) {
         mLastCommittedVsyncTime =
                 TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 18bf416..a91dde7 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -144,7 +144,7 @@
 
     // An elevated number of queued buffers in the server is detected. This propagates a
     // flag to Choreographer indicating that buffer stuffing recovery should begin.
-    virtual void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
+    virtual void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) = 0;
 };
 
 struct IEventThreadCallback {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 2e1f938..91a798e 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -24,7 +24,7 @@
 #include <scheduler/interface/ICompositor.h>
 
 #include "EventThread.h"
-#include "FrameTimeline.h"
+#include "FrameTimeline/FrameTimeline.h"
 #include "MessageQueue.h"
 
 namespace android::impl {
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index f2be316..4dd3ab6 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -33,6 +33,10 @@
     }
 
     bool operator!=(const FrameRateMode& other) const { return !(*this == other); }
+
+    bool matchesResolution(const FrameRateMode& other) const {
+        return modePtr->getResolution() == other.modePtr->getResolution();
+    }
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1dc7b2e..f3db4c5 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1163,8 +1163,8 @@
     }
 
     Mutex::Autolock lock(mStateLock);
-    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
-    const auto displayOpt = mPhysicalDisplays.get(*id).and_then(getDisplayDeviceAndSnapshot());
+    const PhysicalDisplayId id = PhysicalDisplayId::fromValue(static_cast<uint64_t>(displayId));
+    const auto displayOpt = mPhysicalDisplays.get(id).and_then(getDisplayDeviceAndSnapshot());
 
     if (!displayOpt) {
         return NAME_NOT_FOUND;
@@ -1286,9 +1286,9 @@
 
     Mutex::Autolock lock(mStateLock);
 
-    const auto id_ =
-            DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(physicalDisplayId));
-    const auto displayOpt = mPhysicalDisplays.get(*id_).and_then(getDisplayDeviceAndSnapshot());
+    const PhysicalDisplayId id =
+            PhysicalDisplayId::fromValue(static_cast<uint64_t>(physicalDisplayId));
+    const auto displayOpt = mPhysicalDisplays.get(id).and_then(getDisplayDeviceAndSnapshot());
 
     if (!displayOpt) {
         return NAME_NOT_FOUND;
@@ -1363,7 +1363,8 @@
             const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId);
             if (!selectorPtr) break;
 
-            const Fps renderRate = selectorPtr->getActiveMode().fps;
+            const auto activeMode = selectorPtr->getActiveMode();
+            const Fps renderRate = activeMode.fps;
 
             // DisplayModeController::setDesiredMode updated the render rate, so inform Scheduler.
             mScheduler->setRenderRate(displayId, renderRate, true /* applyImmediately */);
@@ -1382,6 +1383,15 @@
 
             mScheduler->updatePhaseConfiguration(displayId, mode.fps);
             mScheduler->setModeChangePending(true);
+
+            // The mode set to switch resolution is not initiated until the display transaction that
+            // resizes the display. DM sends this transaction in response to a mode change event, so
+            // emit the event now, not when finalizing the mode change as for a refresh rate switch.
+            if (FlagManager::getInstance().synced_resolution_switch() &&
+                !mode.matchesResolution(activeMode)) {
+                mScheduler->onDisplayModeChanged(displayId, mode,
+                                                 /*clearContentRequirements*/ true);
+            }
             break;
         }
         case DesiredModeAction::InitiateRenderRateSwitch:
@@ -1460,19 +1470,25 @@
     }
 
     const auto& activeMode = pendingModeOpt->mode;
+    const bool resolutionMatch = !FlagManager::getInstance().synced_resolution_switch() ||
+            activeMode.matchesResolution(mDisplayModeController.getActiveMode(displayId));
 
-    if (const auto oldResolution =
-                mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
-        oldResolution != activeMode.modePtr->getResolution()) {
-        auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId));
-        // We need to generate new sequenceId in order to recreate the display (and this
-        // way the framebuffer).
-        state.sequenceId = DisplayDeviceState{}.sequenceId;
-        state.physical->activeMode = activeMode.modePtr.get();
-        processDisplayChangesLocked();
+    if (!FlagManager::getInstance().synced_resolution_switch()) {
+        if (const auto oldResolution =
+                    mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
+            oldResolution != activeMode.modePtr->getResolution()) {
+            auto& state =
+                    mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId));
+            // We need to generate new sequenceId in order to recreate the display (and this
+            // way the framebuffer).
+            state.sequenceId = DisplayDeviceState{}.sequenceId;
+            state.physical->activeMode = activeMode.modePtr.get();
+            processDisplayChangesLocked();
 
-        // The DisplayDevice has been destroyed, so abort the commit for the now dead FrameTargeter.
-        return false;
+            // The DisplayDevice has been destroyed, so abort the commit for the now dead
+            // FrameTargeter.
+            return false;
+        }
     }
 
     mDisplayModeController.finalizeModeChange(displayId, activeMode.modePtr->getId(),
@@ -1480,7 +1496,8 @@
 
     mScheduler->updatePhaseConfiguration(displayId, activeMode.fps);
 
-    if (pendingModeOpt->emitEvent) {
+    // Skip for resolution changes, since the event was already emitted on setting the desired mode.
+    if (resolutionMatch && pendingModeOpt->emitEvent) {
         mScheduler->onDisplayModeChanged(displayId, activeMode, /*clearContentRequirements*/ true);
     }
 
@@ -1532,8 +1549,9 @@
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(displayId).c_str());
 
-        if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
-            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+        const auto activeMode = mDisplayModeController.getActiveMode(displayId);
+
+        if (!desiredModeOpt->force && desiredModeOpt->mode == activeMode) {
             applyActiveMode(displayId);
             continue;
         }
@@ -1554,6 +1572,15 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
+        // When initiating a resolution change, wait until the commit that resizes the display.
+        if (FlagManager::getInstance().synced_resolution_switch() &&
+            !activeMode.matchesResolution(desiredModeOpt->mode)) {
+            const auto display = getDisplayDeviceLocked(displayId);
+            if (display->getSize() != desiredModeOpt->mode.modePtr->getResolution()) {
+                continue;
+            }
+        }
+
         const auto error =
                 mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
                                                           constraints, outTimeline);
@@ -2977,6 +3004,8 @@
     int index = 0;
     ftl::StaticVector<char, WorkloadTracer::COMPOSITION_SUMMARY_SIZE> compositionSummary;
     auto lastLayerStack = ui::INVALID_LAYER_STACK;
+
+    uint64_t prevOverrideBufferId = 0;
     for (auto& [layer, layerFE] : layers) {
         CompositionResult compositionResult{layerFE->stealCompositionResult()};
         if (lastLayerStack != layerFE->mSnapshot->outputFilter.layerStack) {
@@ -2986,8 +3015,37 @@
             }
             lastLayerStack = layerFE->mSnapshot->outputFilter.layerStack;
         }
+
+        // If there are N layers in a cached set they should all share the same buffer id.
+        // The first layer in the cached set will be not skipped and layers 1..N-1 will be skipped.
+        // We expect all layers in the cached set to be marked as composited by HWC.
+        // Here is a made up example of how it is visualized
+        //
+        //      [b:rrc][s:cc]
+        //
+        // This should be interpreted to mean that there are 2 cached sets.
+        // So there are only 2 non skipped layers -- b and s.
+        // The layers rrc and cc are flattened into layers b and s respectively.
+        const LayerFE::HwcLayerDebugState &hwcState = layerFE->getLastHwcState();
+        if (hwcState.overrideBufferId != prevOverrideBufferId) {
+            // End the existing run.
+            if (prevOverrideBufferId) {
+                compositionSummary.push_back(']');
+            }
+            // Start a new run.
+            if (hwcState.overrideBufferId) {
+                compositionSummary.push_back('[');
+            }
+        }
+
         compositionSummary.push_back(
-                layerFE->mSnapshot->classifyCompositionForDebug(layerFE->getHwcCompositionType()));
+                layerFE->mSnapshot->classifyCompositionForDebug(hwcState));
+
+        if (hwcState.overrideBufferId && !hwcState.wasSkipped) {
+                compositionSummary.push_back(':');
+        }
+        prevOverrideBufferId = hwcState.overrideBufferId;
+
         if (layerFE->mSnapshot->hasEffect()) {
             compositedWorkload |= adpf::Workload::EFFECTS;
         }
@@ -2999,6 +3057,10 @@
             mActivePictureTracker.onLayerComposed(*layer, *layerFE, compositionResult);
         }
     }
+    // End the last run.
+    if (prevOverrideBufferId) {
+        compositionSummary.push_back(']');
+    }
 
     // Concisely describe the layers composited this frame using single chars. GPU composited layers
     // are uppercase, DPU composited are lowercase. Special chars denote effects (blur, shadow,
@@ -3424,13 +3486,7 @@
     mTimeStats->setPresentFenceGlobal(pacesetterPresentFenceTime);
 
     for (auto&& [id, presentFence] : presentFences) {
-        ftl::FakeGuard guard(mStateLock);
-        const bool isInternalDisplay =
-                mPhysicalDisplays.get(id).transform(&PhysicalDisplay::isInternal).value_or(false);
-
-        if (isInternalDisplay) {
-            mScheduler->addPresentFence(id, std::move(presentFence));
-        }
+        mScheduler->addPresentFence(id, std::move(presentFence));
     }
 
     const bool hasPacesetterDisplay =
@@ -3718,6 +3774,7 @@
     if (const auto displayOpt = mPhysicalDisplays.get(displayId)) {
         const auto& display = displayOpt->get();
         const auto& snapshot = display.snapshot();
+        const uint8_t port = snapshot.port();
 
         std::optional<DeviceProductInfo> deviceProductInfo;
         if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) {
@@ -3729,14 +3786,14 @@
         // Use the cached port via snapshot because we are updating an existing
         // display on reconnect.
         const auto it =
-                mPhysicalDisplays.try_replace(displayId, display.token(), displayId,
-                                              snapshot.port(), snapshot.connectionType(),
-                                              std::move(displayModes), std::move(colorModes),
-                                              std::move(deviceProductInfo));
+                mPhysicalDisplays.try_replace(displayId, display.token(), displayId, port,
+                                              snapshot.connectionType(), std::move(displayModes),
+                                              std::move(colorModes), std::move(deviceProductInfo));
 
         auto& state = mCurrentState.displays.editValueFor(it->second.token());
         state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
         state.physical->activeMode = std::move(activeMode);
+        state.physical->port = port;
         ALOGI("Reconnecting %s", displayString);
         return activeModeId;
     }
@@ -3752,6 +3809,7 @@
     DisplayDeviceState state;
     state.physical = {.id = displayId,
                       .hwcDisplayId = hwcDisplayId,
+                      .port = info.port,
                       .activeMode = std::move(activeMode)};
     if (mIsHdcpViaNegVsync) {
         state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
@@ -4067,7 +4125,7 @@
 
         if (const auto& physical = currentState.physical) {
             getHwComposer().allocatePhysicalDisplay(physical->hwcDisplayId, physical->id,
-                                                    /*physicalSize=*/std::nullopt);
+                                                    physical->port, /*physicalSize=*/std::nullopt);
         }
 
         processDisplayAdded(displayToken, currentState);
@@ -4093,6 +4151,35 @@
         if (currentState.flags != drawingState.flags) {
             display->setFlags(currentState.flags);
         }
+
+        const auto updateDisplaySize = [&]() {
+            if (currentState.width != drawingState.width ||
+                currentState.height != drawingState.height) {
+                const ui::Size resolution = ui::Size(currentState.width, currentState.height);
+
+                // Resize the framebuffer. For a virtual display, always do so. For a physical
+                // display, only do so if it has a pending modeset for the matching resolution.
+                if (!currentState.physical ||
+                    (FlagManager::getInstance().synced_resolution_switch() &&
+                     mDisplayModeController.getDesiredMode(display->getPhysicalId())
+                             .transform([resolution](const auto& request) {
+                                 return resolution == request.mode.modePtr->getResolution();
+                             })
+                             .value_or(false))) {
+                    display->setDisplaySize(resolution);
+                }
+
+                if (display->getId() == mActiveDisplayId) {
+                    onActiveDisplaySizeChanged(*display);
+                }
+            }
+        };
+
+        if (FlagManager::getInstance().synced_resolution_switch()) {
+            // Update display size first, as display projection below depends on it.
+            updateDisplaySize();
+        }
+
         if ((currentState.orientation != drawingState.orientation) ||
             (currentState.layerStackSpaceRect != drawingState.layerStackSpaceRect) ||
             (currentState.orientedDisplaySpaceRect != drawingState.orientedDisplaySpaceRect)) {
@@ -4104,13 +4191,9 @@
                         ui::Transform::toRotationFlags(display->getOrientation());
             }
         }
-        if (currentState.width != drawingState.width ||
-            currentState.height != drawingState.height) {
-            display->setDisplaySize(currentState.width, currentState.height);
 
-            if (display->getId() == mActiveDisplayId) {
-                onActiveDisplaySizeChanged(*display);
-            }
+        if (!FlagManager::getInstance().synced_resolution_switch()) {
+            updateDisplaySize();
         }
     }
 }
@@ -6731,8 +6814,9 @@
                         return getDefaultDisplayDevice()->getDisplayToken().promote();
                     }
 
-                    if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(value)) {
-                        return getPhysicalDisplayToken(*id);
+                    if (const auto token =
+                                getPhysicalDisplayToken(PhysicalDisplayId::fromValue(value))) {
+                        return token;
                     }
 
                     ALOGE("Invalid physical display ID");
@@ -6830,10 +6914,10 @@
             case 1040: {
                 auto future = mScheduler->schedule([&] {
                     n = data.readInt32();
-                    std::optional<PhysicalDisplayId> inputId = std::nullopt;
+                    PhysicalDisplayId inputId;
                     if (uint64_t inputDisplayId; data.readUint64(&inputDisplayId) == NO_ERROR) {
-                        inputId = DisplayId::fromValue<PhysicalDisplayId>(inputDisplayId);
-                        if (!inputId || getPhysicalDisplayToken(*inputId)) {
+                        inputId = PhysicalDisplayId::fromValue(inputDisplayId);
+                        if (!getPhysicalDisplayToken(inputId)) {
                             ALOGE("No display with id: %" PRIu64, inputDisplayId);
                             return NAME_NOT_FOUND;
                         }
@@ -6842,7 +6926,7 @@
                         Mutex::Autolock lock(mStateLock);
                         mLayerCachingEnabled = n != 0;
                         for (const auto& [_, display] : mDisplays) {
-                            if (!inputId || *inputId == display->getPhysicalId()) {
+                            if (inputId == display->getPhysicalId()) {
                                 display->enableLayerCaching(mLayerCachingEnabled);
                             }
                         }
@@ -6925,11 +7009,10 @@
                         int64_t arg1 = data.readInt64();
                         int64_t arg2 = data.readInt64();
                         // Enable mirroring for one display
-                        const auto display1id = DisplayId::fromValue(arg1);
                         auto mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(
-                                display1id.value());
-                        auto id2 = DisplayId::fromValue<PhysicalDisplayId>(arg2);
-                        const auto token2 = getPhysicalDisplayToken(*id2);
+                                DisplayId::fromValue(arg1));
+                        const auto token2 =
+                                getPhysicalDisplayToken(PhysicalDisplayId::fromValue(arg2));
                         ui::LayerStack layerStack;
                         {
                             Mutex::Autolock lock(mStateLock);
@@ -7161,14 +7244,13 @@
 
 namespace {
 
-ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace,
-                                const compositionengine::impl::OutputCompositionState& state,
+ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, ui::ColorMode colorMode,
                                 bool capturingHdrLayers, bool hintForSeamlessTransition) {
     if (requestedDataspace != ui::Dataspace::UNKNOWN) {
         return requestedDataspace;
     }
 
-    const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
+    const auto dataspaceForColorMode = ui::pickDataspaceFor(colorMode);
 
     // TODO: Enable once HDR screenshots are ready.
     if constexpr (/* DISABLES CODE */ (false)) {
@@ -7480,11 +7562,11 @@
     return protectedLayerFound;
 }
 
-// Getting layer snapshots and display should take place on main thread.
-// Accessing display requires mStateLock, and contention for this lock
-// is reduced when grabbed from the main thread, thus also reducing
-// risk of deadlocks.
-std::optional<SurfaceFlinger::OutputCompositionState> SurfaceFlinger::getSnapshotsFromMainThread(
+// Getting layer snapshots and accessing display state should take place on
+// main thread. Accessing display requires mStateLock, and contention for
+// this lock is reduced when grabbed from the main thread, thus also reducing
+// risk of deadlocks. Returns false if no display is found.
+bool SurfaceFlinger::getSnapshotsFromMainThread(
         ScreenshotArgs& args, GetLayerSnapshotsFunction getLayerSnapshotsFn,
         std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     return mScheduler
@@ -7524,8 +7606,8 @@
     }
 
     std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
-    auto displayState = getSnapshotsFromMainThread(args, getLayerSnapshotsFn, layers);
-    if (!displayState) {
+    bool hasDisplayState = getSnapshotsFromMainThread(args, getLayerSnapshotsFn, layers);
+    if (!hasDisplayState) {
         ALOGD("Display state not found");
         invokeScreenCaptureError(NO_MEMORY, captureListener);
     }
@@ -7602,12 +7684,13 @@
 
     auto futureFence =
             captureScreenshot(args, texture, false /* regionSampling */, grayscale, isProtected,
-                              captureListener, displayState, layers, hdrTexture, gainmapTexture);
+                              captureListener, layers, hdrTexture, gainmapTexture);
     futureFence.get();
 }
 
-std::optional<SurfaceFlinger::OutputCompositionState> SurfaceFlinger::getDisplayStateOnMainThread(
-        ScreenshotArgs& args) {
+// Returns true if display is found and args was populated with display state
+// data. Otherwise, returns false.
+bool SurfaceFlinger::getDisplayStateOnMainThread(ScreenshotArgs& args) {
     sp<const DisplayDevice> display = nullptr;
     {
         Mutex::Autolock lock(mStateLock);
@@ -7642,49 +7725,49 @@
         }
 
         if (display != nullptr) {
-            return std::optional{display->getCompositionDisplay()->getState()};
+            const auto& state = display->getCompositionDisplay()->getState();
+            args.displayBrightnessNits = state.displayBrightnessNits;
+            args.sdrWhitePointNits = state.sdrWhitePointNits;
+            args.renderIntent = state.renderIntent;
+            args.colorMode = state.colorMode;
+            return true;
         }
     }
-    return std::nullopt;
+    return false;
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
-        const ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+        ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool regionSampling, bool grayscale, bool isProtected,
         const sp<IScreenCaptureListener>& captureListener,
-        const std::optional<OutputCompositionState>& displayState,
         const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
         const std::shared_ptr<renderengine::ExternalTexture>& hdrBuffer,
         const std::shared_ptr<renderengine::ExternalTexture>& gainmapBuffer) {
     SFTRACE_CALL();
 
     ScreenCaptureResults captureResults;
-
-    float displayBrightnessNits = displayState.value().displayBrightnessNits;
-    float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
-
     ftl::SharedFuture<FenceResult> renderFuture;
 
+    float hdrSdrRatio = args.displayBrightnessNits / args.sdrWhitePointNits;
+
     if (hdrBuffer && gainmapBuffer) {
         ftl::SharedFuture<FenceResult> hdrRenderFuture =
                 renderScreenImpl(args, hdrBuffer, regionSampling, grayscale, isProtected,
-                                 captureResults, displayState, layers);
+                                 captureResults, layers);
         captureResults.buffer = buffer->getBuffer();
         captureResults.optionalGainMap = gainmapBuffer->getBuffer();
 
         renderFuture =
                 ftl::Future(std::move(hdrRenderFuture))
-                        .then([&, displayBrightnessNits, sdrWhitePointNits,
-                               dataspace = captureResults.capturedDataspace, buffer, hdrBuffer,
-                               gainmapBuffer](FenceResult fenceResult) -> FenceResult {
+                        .then([&, hdrSdrRatio, dataspace = captureResults.capturedDataspace, buffer,
+                               hdrBuffer, gainmapBuffer](FenceResult fenceResult) -> FenceResult {
                             if (!fenceResult.ok()) {
                                 return fenceResult;
                             }
 
                             return getRenderEngine()
                                     .tonemapAndDrawGainmap(hdrBuffer, fenceResult.value()->get(),
-                                                           displayBrightnessNits /
-                                                                   sdrWhitePointNits,
+                                                           hdrSdrRatio,
                                                            static_cast<ui::Dataspace>(dataspace),
                                                            buffer, gainmapBuffer)
                                     .get();
@@ -7692,17 +7775,16 @@
                         .share();
     } else {
         renderFuture = renderScreenImpl(args, buffer, regionSampling, grayscale, isProtected,
-                                        captureResults, displayState, layers);
+                                        captureResults, layers);
     }
 
     if (captureListener) {
         // Defer blocking on renderFuture back to the Binder thread.
         return ftl::Future(std::move(renderFuture))
                 .then([captureListener, captureResults = std::move(captureResults),
-                       displayBrightnessNits,
-                       sdrWhitePointNits](FenceResult fenceResult) mutable -> FenceResult {
+                       hdrSdrRatio](FenceResult fenceResult) mutable -> FenceResult {
                     captureResults.fenceResult = std::move(fenceResult);
-                    captureResults.hdrSdrRatio = displayBrightnessNits / sdrWhitePointNits;
+                    captureResults.hdrSdrRatio = hdrSdrRatio;
                     captureListener->onScreenCaptureCompleted(captureResults);
                     return base::unexpected(NO_ERROR);
                 })
@@ -7712,9 +7794,8 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        const ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+        ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        const std::optional<OutputCompositionState>& displayState,
         const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     SFTRACE_CALL();
 
@@ -7728,49 +7809,37 @@
                 layerFE->mSnapshot->geomLayerTransform.inverse();
     }
 
-    auto capturedBuffer = buffer;
-
-    auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
-    auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance;
-    auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance;
-
-    captureResults.capturedDataspace = args.dataspace;
-
     const bool enableLocalTonemapping =
             FlagManager::getInstance().local_tonemap_screenshots() && !args.seamlessTransition;
 
-    if (displayState) {
-        const auto& state = displayState.value();
-        captureResults.capturedDataspace =
-                pickBestDataspace(args.dataspace, state, captureResults.capturedHdrLayers,
-                                  args.seamlessTransition);
-        sdrWhitePointNits = state.sdrWhitePointNits;
+    captureResults.capturedDataspace =
+            pickBestDataspace(args.dataspace, args.colorMode, captureResults.capturedHdrLayers,
+                              args.seamlessTransition);
 
-        if (!captureResults.capturedHdrLayers) {
-            displayBrightnessNits = sdrWhitePointNits;
-        } else {
-            displayBrightnessNits = state.displayBrightnessNits;
-            if (!enableLocalTonemapping) {
-                // Only clamp the display brightness if this is not a seamless transition.
-                // Otherwise for seamless transitions it's important to match the current
-                // display state as the buffer will be shown under these same conditions, and we
-                // want to avoid any flickers
-                if (sdrWhitePointNits > 1.0f && !args.seamlessTransition) {
-                    // Restrict the amount of HDR "headroom" in the screenshot to avoid
-                    // over-dimming the SDR portion. 2.0 chosen by experimentation
-                    constexpr float kMaxScreenshotHeadroom = 2.0f;
-                    displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
-                                                     displayBrightnessNits);
-                }
-            }
-        }
-
-        // Screenshots leaving the device should be colorimetric
-        if (args.dataspace == ui::Dataspace::UNKNOWN && args.seamlessTransition) {
-            renderIntent = state.renderIntent;
-        }
+    // Only clamp the display brightness if this is not a seamless transition.
+    // Otherwise for seamless transitions it's important to match the current
+    // display state as the buffer will be shown under these same conditions, and we
+    // want to avoid any flickers.
+    if (captureResults.capturedHdrLayers && !enableLocalTonemapping &&
+        args.sdrWhitePointNits > 1.0f && !args.seamlessTransition) {
+        // Restrict the amount of HDR "headroom" in the screenshot to avoid
+        // over-dimming the SDR portion. 2.0 chosen by experimentation
+        constexpr float kMaxScreenshotHeadroom = 2.0f;
+        // TODO: Aim to update displayBrightnessNits earlier in screenshot
+        // path so ScreenshotArgs can be passed as const
+        args.displayBrightnessNits = std::min(args.sdrWhitePointNits * kMaxScreenshotHeadroom,
+                                              args.displayBrightnessNits);
+    } else {
+        args.displayBrightnessNits = args.sdrWhitePointNits;
     }
 
+    auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
+    // Screenshots leaving the device should be colorimetric
+    if (args.dataspace == ui::Dataspace::UNKNOWN && args.seamlessTransition) {
+        renderIntent = args.renderIntent;
+    }
+
+    auto capturedBuffer = buffer;
     captureResults.buffer = capturedBuffer->getBuffer();
 
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
@@ -7780,8 +7849,7 @@
     }
 
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
-                    sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected, layers,
-                    layerStack, regionSampling, args, renderIntent,
+                    grayscale, isProtected, layers, layerStack, regionSampling, args, renderIntent,
                     enableLocalTonemapping]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
@@ -7807,9 +7875,10 @@
         if (enableLocalTonemapping) {
             // Boost the whole scene so that SDR white is at 1.0 while still communicating the hdr
             // sdr ratio via display brightness / sdrWhite nits.
-            targetBrightness = sdrWhitePointNits / displayBrightnessNits;
+            targetBrightness = args.sdrWhitePointNits / args.displayBrightnessNits;
         } else if (dataspace == ui::Dataspace::BT2020_HLG) {
-            const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203;
+            const float maxBrightnessNits =
+                    args.displayBrightnessNits / args.sdrWhitePointNits * 203;
             // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content
             // will appear way too bright.
             if (maxBrightnessNits < 1000.f) {
@@ -7835,8 +7904,8 @@
                                         .buffer = std::move(buffer),
                                         .displayId = args.displayId,
                                         .reqBufferSize = args.reqSize,
-                                        .sdrWhitePointNits = sdrWhitePointNits,
-                                        .displayBrightnessNits = displayBrightnessNits,
+                                        .sdrWhitePointNits = args.sdrWhitePointNits,
+                                        .displayBrightnessNits = args.displayBrightnessNits,
                                         .targetBrightness = targetBrightness,
                                         .layerAlpha = layerAlpha,
                                         .regionSampling = regionSampling,
@@ -8714,8 +8783,8 @@
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
-    *outDisplay = mFlinger->getPhysicalDisplayToken(*id);
+    const PhysicalDisplayId id = PhysicalDisplayId::fromValue(static_cast<uint64_t>(displayId));
+    *outDisplay = mFlinger->getPhysicalDisplayToken(id);
     return binder::Status::ok();
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 935a2da..3f454ba 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -896,32 +896,41 @@
         // If true, the render result may be used for system animations
         // that must preserve the exact colors of the display
         bool seamlessTransition{false};
+
+        // Current display brightness of the output composition state
+        float displayBrightnessNits{-1.f};
+
+        // SDR white point of the output composition state
+        float sdrWhitePointNits{-1.f};
+
+        // Current active color mode of the output composition state
+        ui::ColorMode colorMode{ui::ColorMode::NATIVE};
+
+        // Current active render intent of the output composition state
+        ui::RenderIntent renderIntent{ui::RenderIntent::COLORIMETRIC};
     };
 
-    std::optional<OutputCompositionState> getSnapshotsFromMainThread(
-            ScreenshotArgs& args, GetLayerSnapshotsFunction getLayerSnapshotsFn,
-            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
+    bool getSnapshotsFromMainThread(ScreenshotArgs& args,
+                                    GetLayerSnapshotsFunction getLayerSnapshotsFn,
+                                    std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     void captureScreenCommon(ScreenshotArgs& args, GetLayerSnapshotsFunction, ui::Size bufferSize,
                              ui::PixelFormat, bool allowProtected, bool grayscale,
                              const sp<IScreenCaptureListener>&);
 
-    std::optional<OutputCompositionState> getDisplayStateOnMainThread(ScreenshotArgs& args)
-            REQUIRES(kMainThreadContext);
+    bool getDisplayStateOnMainThread(ScreenshotArgs& args) REQUIRES(kMainThreadContext);
 
     ftl::SharedFuture<FenceResult> captureScreenshot(
-            const ScreenshotArgs& args,
-            const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
-            const std::optional<OutputCompositionState>& displayState,
+            ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+            bool regionSampling, bool grayscale, bool isProtected,
+            const sp<IScreenCaptureListener>& captureListener,
             const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
             const std::shared_ptr<renderengine::ExternalTexture>& hdrBuffer = nullptr,
             const std::shared_ptr<renderengine::ExternalTexture>& gainmapBuffer = nullptr);
 
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            const ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>&,
+            ScreenshotArgs& args, const std::shared_ptr<renderengine::ExternalTexture>&,
             bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&,
-            const std::optional<OutputCompositionState>& displayState,
             const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     void readPersistentProperties();
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index b1552e6..f9aba9f 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -171,6 +171,7 @@
     DUMP_ACONFIG_FLAG(restore_blur_step);
     DUMP_ACONFIG_FLAG(skip_invisible_windows_in_input);
     DUMP_ACONFIG_FLAG(stable_edid_ids);
+    DUMP_ACONFIG_FLAG(synced_resolution_switch);
     DUMP_ACONFIG_FLAG(trace_frame_rate_override);
     DUMP_ACONFIG_FLAG(true_hdr_screenshots);
     DUMP_ACONFIG_FLAG(use_known_refresh_rate_for_fps_consistency);
@@ -295,6 +296,7 @@
 FLAG_MANAGER_ACONFIG_FLAG(begone_bright_hlg, "debug.sf.begone_bright_hlg");
 FLAG_MANAGER_ACONFIG_FLAG(window_blur_kawase2, "");
 FLAG_MANAGER_ACONFIG_FLAG(reject_dupe_layerstacks, "");
+FLAG_MANAGER_ACONFIG_FLAG(synced_resolution_switch, "");
 
 /// Trunk stable server (R/W) flags ///
 FLAG_MANAGER_ACONFIG_FLAG(refresh_rate_overlay_on_external_display, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 073302e..de3f359 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -107,6 +107,7 @@
     bool restore_blur_step() const;
     bool skip_invisible_windows_in_input() const;
     bool stable_edid_ids() const;
+    bool synced_resolution_switch() const;
     bool trace_frame_rate_override() const;
     bool true_hdr_screenshots() const;
     bool use_known_refresh_rate_for_fps_consistency() const;
diff --git a/services/surfaceflinger/common/include/common/WorkloadTracer.h b/services/surfaceflinger/common/include/common/WorkloadTracer.h
index 39b6fa1..c4074f7 100644
--- a/services/surfaceflinger/common/include/common/WorkloadTracer.h
+++ b/services/surfaceflinger/common/include/common/WorkloadTracer.h
@@ -23,7 +23,7 @@
 
 static constexpr int32_t COMPOSITION_TRACE_COOKIE = 1;
 static constexpr int32_t POST_COMPOSITION_TRACE_COOKIE = 2;
-static constexpr size_t COMPOSITION_SUMMARY_SIZE = 20;
+static constexpr size_t COMPOSITION_SUMMARY_SIZE = 64;
 static constexpr const char* TRACK_NAME = "CriticalWorkload";
 
 } // namespace android::WorkloadTracer
\ No newline at end of file
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 96ab7ab..fa1da45 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -265,6 +265,13 @@
 } # stable_edid_ids
 
 flag {
+  name: "synced_resolution_switch"
+  namespace: "core_graphics"
+  description: "Synchronize resolution modeset with framebuffer resizing"
+  bug: "355427258"
+} # synced_resolution_switch
+
+flag {
   name: "true_hdr_screenshots"
   namespace: "core_graphics"
   description: "Enables screenshotting display content in HDR, sans tone mapping"
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index b4496d3..5a82914 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -147,7 +147,7 @@
                 << "Timeout waiting for vsync event";
         DisplayEventReceiver::Event event;
         while (mDisplayEventReceiver.getEvents(&event, 1) > 0) {
-            if (event.header.type != DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+            if (event.header.type != DisplayEventType::DISPLAY_EVENT_VSYNC) {
                 continue;
             }
 
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index ee5d919..7910e77 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -497,17 +497,6 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
-    void setClientDrawnShadowRadius(uint32_t id, float clientDrawnShadowRadius) {
-        std::vector<QueuedTransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eClientDrawnShadowsChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.clientDrawnShadowRadius = clientDrawnShadowRadius;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
     void setShadowRadius(uint32_t id, float shadowRadius) {
         std::vector<QueuedTransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 42259af..f1c1549 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -65,9 +65,9 @@
         "mock/MockFrameTracer.cpp",
         "mock/MockNativeWindowSurface.cpp",
         "mock/MockTimeStats.cpp",
-        "mock/MockVsyncController.cpp",
         "mock/MockVSyncDispatch.cpp",
         "mock/MockVSyncTracker.cpp",
+        "mock/MockVsyncController.cpp",
     ],
 }
 
@@ -87,10 +87,10 @@
     test_suites: ["device-tests"],
     header_libs: ["surfaceflinger_tests_common_headers"],
     srcs: [
+        "*.cpp",
         ":libsurfaceflinger_backend_mock_sources",
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
-        "*.cpp",
     ],
 }
 
@@ -117,9 +117,8 @@
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
         "libaidlcommonsupport",
-        "libcompositionengine_mocks",
         "libcompositionengine",
-        "libframetimeline",
+        "libcompositionengine_mocks",
         "libgmock",
         "libgui_mocks",
         "libperfetto_client_experimental",
@@ -140,14 +139,15 @@
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.common@1.2",
+        "libEGL",
+        "libGLESv1_CM",
+        "libGLESv2",
+        "libSurfaceFlingerProp",
         "libbase",
         "libbinder",
         "libbinder_ndk",
         "libcutils",
-        "libEGL",
         "libfmq",
-        "libGLESv1_CM",
-        "libGLESv2",
         "libgui",
         "libhidlbase",
         "libinput",
@@ -157,11 +157,10 @@
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libstatslog_surfaceflinger",
-        "libSurfaceFlingerProp",
         "libsync",
+        "libtracing_perfetto",
         "libui",
         "libutils",
-        "libtracing_perfetto",
     ],
     header_libs: [
         "android.hardware.graphics.composer3-command-buffer",
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 3fead93..7f9296f 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -193,7 +193,7 @@
     }
 };
 
-template <uint64_t displayId>
+template <VirtualDisplayId::BaseId displayId>
 struct DisplayIdGetter<HalVirtualDisplayIdType<displayId>> {
     static HalVirtualDisplayId get() { return HalVirtualDisplayId(displayId); }
 };
@@ -533,13 +533,14 @@
     static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
 };
 
-template <ui::DisplayConnectionType connectionType, bool hasIdentificationData, bool secure>
+template <ui::DisplayConnectionType connectionType, bool hasIdentificationData, bool secure,
+          HWDisplayId hwDisplayId = 1002>
 struct SecondaryDisplay {
     static constexpr auto CONNECTION_TYPE = connectionType;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr bool SECURE = secure;
     static constexpr uint8_t PORT = 254;
-    static constexpr HWDisplayId HWC_DISPLAY_ID = 1002;
+    static constexpr HWDisplayId HWC_DISPLAY_ID = hwDisplayId;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
     static constexpr auto GET_IDENTIFICATION_DATA =
             connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid
@@ -571,10 +572,11 @@
                                                 /*hasIdentificationData=*/true, kNonSecure>,
                                1080, 2092>;
 
-using ExternalDisplayWithIdentificationVariant =
-        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
-                                                /*hasIdentificationData=*/true, kNonSecure>,
-                               1920, 1280>;
+template <HWDisplayId hwDisplayId = 1002>
+using ExternalDisplayWithIdentificationVariant = PhysicalDisplayVariant<
+        SecondaryDisplay<ui::DisplayConnectionType::External,
+                         /*hasIdentificationData=*/true, kNonSecure, hwDisplayId>,
+        1920, 1280>;
 using ExternalDisplayVariant =
         PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
                                                 /*hasIdentificationData=*/false, kSecure>,
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 268a6c4..6f15db8 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -30,7 +30,7 @@
 
 #include "AsyncCallRecorder.h"
 #include "DisplayHardware/DisplayMode.h"
-#include "FrameTimeline.h"
+#include "FrameTimeline/FrameTimeline.h"
 #include "Scheduler/EventThread.h"
 #include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
@@ -270,7 +270,7 @@
     ASSERT_TRUE(args.has_value()) << name << " did not receive an event for timestamp "
                                   << expectedTimestamp;
     const auto& event = std::get<0>(args.value());
-    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_VSYNC, event.header.type)
+    EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_VSYNC, event.header.type)
             << name << " did not get the correct event for timestamp " << expectedTimestamp;
     EXPECT_EQ(expectedTimestamp, event.header.timestamp)
             << name << " did not get the expected timestamp for timestamp " << expectedTimestamp;
@@ -344,7 +344,7 @@
     auto args = mConnectionEventCallRecorder.waitForCall();
     ASSERT_TRUE(args.has_value());
     const auto& event = std::get<0>(args.value());
-    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, event.header.type);
+    EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_HOTPLUG, event.header.type);
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
     EXPECT_EQ(expectedConnected, event.hotplug.connected);
 }
@@ -355,7 +355,7 @@
     auto args = mConnectionEventCallRecorder.waitForCall();
     ASSERT_TRUE(args.has_value());
     const auto& event = std::get<0>(args.value());
-    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, event.header.type);
+    EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_MODE_CHANGE, event.header.type);
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
     EXPECT_EQ(expectedConfigId, event.modeChange.modeId);
     EXPECT_EQ(expectedVsyncPeriod, event.modeChange.vsyncPeriod);
@@ -367,7 +367,7 @@
         auto args = mConnectionEventCallRecorder.waitForCall();
         ASSERT_TRUE(args.has_value());
         const auto& event = std::get<0>(args.value());
-        EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE, event.header.type);
+        EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE, event.header.type);
         EXPECT_EQ(expectedDisplayId, event.header.displayId);
         EXPECT_EQ(uid, event.frameRateOverride.uid);
         EXPECT_EQ(frameRateHz, event.frameRateOverride.frameRateHz);
@@ -376,7 +376,7 @@
     auto args = mConnectionEventCallRecorder.waitForCall();
     ASSERT_TRUE(args.has_value());
     const auto& event = std::get<0>(args.value());
-    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH, event.header.type);
+    EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH, event.header.type);
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
 }
 
@@ -874,7 +874,7 @@
     auto args = mConnectionEventCallRecorder.waitForCall();
     ASSERT_TRUE(args.has_value());
     const auto& event = std::get<0>(args.value());
-    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, event.header.type);
+    EXPECT_EQ(DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, event.header.type);
     EXPECT_EQ(EXTERNAL_DISPLAY_ID, event.header.displayId);
     EXPECT_EQ(HDCP_V1, event.hdcpLevelsChange.connectedLevel);
     EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel);
diff --git a/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
index 2941a14..0f16073 100644
--- a/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
@@ -213,4 +213,33 @@
     EXPECT_EQ(listenerCount(), 0u);
 }
 
-} // namespace android
\ No newline at end of file
+TEST_F(JankTrackerTest, multipleLayersAreTrackedIndependently) {
+    size_t jankDataReceived = 0;
+    size_t numBatchesReceived = 0;
+
+    EXPECT_CALL(*mListener.get(), onJankData(_))
+            .WillRepeatedly([&](const std::vector<gui::JankData>& jankData) {
+                jankDataReceived += jankData.size();
+                numBatchesReceived++;
+                return binder::Status::ok();
+            });
+    addJankListener(123);
+    addJankListener(321);
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    addJankData(321, 4);
+    addJankData(321, 5);
+
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+    EXPECT_EQ(numBatchesReceived, 1u);
+    EXPECT_EQ(jankDataReceived, 3u);
+
+    JankTracker::flushJankData(321);
+    flushBackgroundThread();
+    EXPECT_EQ(numBatchesReceived, 2u);
+    EXPECT_EQ(jankDataReceived, 5u);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 453c053..7aad84b 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1505,14 +1505,6 @@
     EXPECT_EQ(getSnapshot({.id = 111})->roundedCorner.radius.x, RADIUS);
 }
 
-TEST_F(LayerSnapshotTest, ignoreShadows) {
-    static constexpr float SHADOW_RADIUS = 123.f;
-    setClientDrawnShadowRadius(1, SHADOW_RADIUS);
-    setShadowRadius(1, SHADOW_RADIUS);
-    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot({.id = 1})->shadowSettings.length, 0.f);
-}
-
 TEST_F(LayerSnapshotTest, setShadowRadius) {
     static constexpr float SHADOW_RADIUS = 123.f;
     setShadowRadius(1, SHADOW_RADIUS);
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 908637a..e9b86b2 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -22,7 +22,7 @@
 
 #include <scheduler/interface/ICompositor.h>
 
-#include "FrameTimeline.h"
+#include "FrameTimeline/FrameTimeline.h"
 #include "Scheduler/MessageQueue.h"
 #include "mock/MockVSyncDispatch.h"
 #include "utils/Timers.h"
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 2d3ebb4..aadff76 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -27,7 +27,8 @@
 
 class CreateDisplayTest : public DisplayTransactionTest {
 public:
-    void createDisplayWithRequestedRefreshRate(const std::string& name, uint64_t displayId,
+    void createDisplayWithRequestedRefreshRate(const std::string& name,
+                                               VirtualDisplayId::BaseId baseId,
                                                float pacesetterDisplayRefreshRate,
                                                float requestedRefreshRate,
                                                float expectedAdjustedRefreshRate) {
@@ -49,12 +50,10 @@
         EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate));
         EXPECT_EQ(name.c_str(), display.displayName);
 
-        std::optional<VirtualDisplayId> vid =
-                DisplayId::fromValue<VirtualDisplayId>(displayId | DisplayId::FLAG_VIRTUAL);
-        ASSERT_TRUE(vid.has_value());
-
+        const VirtualDisplayId vid = GpuVirtualDisplayId(baseId);
         sp<DisplayDevice> device =
-                mFlinger.createVirtualDisplayDevice(displayToken, *vid, requestedRefreshRate);
+                mFlinger.createVirtualDisplayDevice(displayToken, vid, requestedRefreshRate);
+
         EXPECT_TRUE(device->isVirtual());
         device->adjustRefreshRate(Fps::fromValue(pacesetterDisplayRefreshRate));
         // verifying desired value
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 2d986c6..b0cda0f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -66,7 +66,7 @@
     PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
     PrimaryDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(PrimaryDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -77,12 +77,12 @@
     mFlinger.configure();
 
     // Configure an external display with identification info.
-    using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
+    using ExternalDisplay = ExternalDisplayWithIdentificationVariant<>;
     ExternalDisplay::setupHwcHotplugCallExpectations(this);
     ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
     ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -125,7 +125,7 @@
     PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
     PrimaryDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(PrimaryDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -136,12 +136,12 @@
     mFlinger.configure();
 
     // Configure an external display with identification info.
-    using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
+    using ExternalDisplay = ExternalDisplayWithIdentificationVariant<>;
     ExternalDisplay::setupHwcHotplugCallExpectations(this);
     ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
     ExternalDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -198,7 +198,7 @@
     ExternalDisplay::setupHwcHotplugCallExpectations(this);
     ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -242,7 +242,7 @@
     EXPECT_CALL(*mComposer, getActiveConfig(ExternalDisplay::HWC_DISPLAY_ID, _))
             .WillRepeatedly(Return(Error::BAD_DISPLAY));
 
-    // TODO(b/241286146): Remove this unnecessary call.
+    // TODO: b/241286146 - Remove this unnecessary call.
     EXPECT_CALL(*mComposer,
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
@@ -262,4 +262,53 @@
     EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
 }
 
+TEST_F(HotplugTest, rejectsHotplugOnActivePortsDuplicate) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // Inject a primary display.
+    PrimaryDisplayVariant::injectHwcDisplay(this);
+
+    // Second display should come up properly.
+    using SecondDisplay = ExternalDisplayWithIdentificationVariant<>;
+    SecondDisplay::setupHwcHotplugCallExpectations(this);
+    SecondDisplay::setupHwcGetActiveConfigCallExpectations(this);
+
+    // TODO: b/241286146 - Remove this unnecessary call.
+    EXPECT_CALL(*mComposer,
+                setVsyncEnabled(SecondDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+            .WillOnce(Return(Error::NONE));
+
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
+
+    SecondDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
+    mFlinger.configure();
+
+    EXPECT_TRUE(hasPhysicalHwcDisplay(SecondDisplay::HWC_DISPLAY_ID));
+
+    // Third display will return the same port ID as the second, and the hotplug
+    // should fail.
+    constexpr HWDisplayId kHwDisplayId = 1234;
+    using DuplicatePortDisplay = ExternalDisplayWithIdentificationVariant<kHwDisplayId>;
+
+    // We expect display identification to be fetched correctly, since EDID and
+    // port are available and successfully retrieved from HAL.
+    EXPECT_CALL(*mComposer,
+                getDisplayIdentificationData(DuplicatePortDisplay::HWC_DISPLAY_ID, _, _))
+            .WillOnce(DoAll(SetArgPointee<1>(*DuplicatePortDisplay::PORT::value),
+                            SetArgPointee<2>(getExternalEedid()), Return(Error::NONE)));
+
+    DuplicatePortDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Connected);
+    mFlinger.configure();
+
+    // The hotplug should be rejected due to an attempt to connect a display to an already active
+    // port. No HWComposer::DisplayData should be created.
+    EXPECT_FALSE(hasPhysicalHwcDisplay(DuplicatePortDisplay::HWC_DISPLAY_ID));
+
+    // Disconnecting a display that was not successfully configured should be a no-op.
+    DuplicatePortDisplay::injectPendingHotplugEvent(this, HWComposer::HotplugEvent::Disconnected);
+    mFlinger.configure();
+
+    EXPECT_FALSE(hasPhysicalHwcDisplay(DuplicatePortDisplay::HWC_DISPLAY_ID));
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 6951eaf..cd554ea 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -241,7 +241,8 @@
         ASSERT_TRUE(hwcDisplayId);
         const auto port = Case::Display::PORT::value;
         ASSERT_TRUE(port);
-        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId, std::nullopt);
+        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId, *port,
+                                                         std::nullopt);
         DisplayModePtr activeMode = DisplayMode::Builder(Case::Display::HWC_ACTIVE_CONFIG_ID)
                                             .setResolution(Case::Display::RESOLUTION)
                                             .setVsyncPeriod(DEFAULT_VSYNC_PERIOD)
@@ -252,6 +253,7 @@
 
         state.physical = {.id = *displayId,
                           .hwcDisplayId = *hwcDisplayId,
+                          .port = *port,
                           .activeMode = activeMode};
 
         ui::ColorModes colorModes;
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 9a2e254..41f2b6e 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -469,7 +469,7 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         ScreenCaptureResults captureResults;
-        auto displayState = std::optional{display->getCompositionDisplay()->getState()};
+        const auto& state = display->getCompositionDisplay()->getState();
         auto layers = getLayerSnapshotsFn();
 
         SurfaceFlinger::ScreenshotArgs screenshotArgs;
@@ -480,10 +480,14 @@
         screenshotArgs.dataspace = dataspace;
         screenshotArgs.isSecure = isSecure;
         screenshotArgs.seamlessTransition = seamlessTransition;
+        screenshotArgs.displayBrightnessNits = state.displayBrightnessNits;
+        screenshotArgs.sdrWhitePointNits = state.sdrWhitePointNits;
+        screenshotArgs.renderIntent = state.renderIntent;
+        screenshotArgs.colorMode = state.colorMode;
 
         return mFlinger->renderScreenImpl(screenshotArgs, buffer, regionSampling,
                                           false /* grayscale */, false /* isProtected */,
-                                          captureResults, displayState, layers);
+                                          captureResults, layers);
     }
 
     auto getLayerSnapshotsForScreenshotsFn(ui::LayerStack layerStack, uint32_t uid) {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
index 3fa4093..01d078b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
@@ -44,7 +44,8 @@
     MOCK_METHOD(bool, allocateVirtualDisplay, (HalVirtualDisplayId, ui::Size, ui::PixelFormat*),
                 (override));
     MOCK_METHOD(void, allocatePhysicalDisplay,
-                (hal::HWDisplayId, PhysicalDisplayId, std::optional<ui::Size>), (override));
+                (hal::HWDisplayId, PhysicalDisplayId, uint8_t port, std::optional<ui::Size>),
+                (override));
 
     MOCK_METHOD(std::shared_ptr<HWC2::Layer>, createLayer, (HalDisplayId), (override));
     MOCK_METHOD(status_t, getDeviceCompositionChanges,
diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp
index 8b4a6be..77a68d9 100644
--- a/services/surfaceflinger/tests/vsync/vsync.cpp
+++ b/services/surfaceflinger/tests/vsync/vsync.cpp
@@ -41,7 +41,7 @@
 
     while ((n = q->getEvents(buffer, 1)) > 0) {
         for (int i=0 ; i<n ; i++) {
-            if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+            if (buffer[i].header.type == DisplayEventType::DISPLAY_EVENT_VSYNC) {
                 printf("event vsync: count=%d\t", buffer[i].vsync.count);
             }
             if (oldTimeStamp) {