Merge "SF: Add a sysprop for game default frame rate" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index cd8f3cd..9c01169 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,7 +7,8 @@
           "include-filter": "*"
         },
         {
-          "exclude-filter": "*ChildLayerTest#ChildrenSurviveParentDestruction"
+          // TODO(b/305717998): Deflake and re-enable
+          "exclude-filter": "*ChildLayerTest*"
         }
       ]
     },
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index d73a30b..e00c2a2 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -24,7 +24,6 @@
 
     shared_libs: [
         "libbase",
-        "libbinder", // also contains servicemanager_interface
         "libvintf",
         "libcutils",
         "liblog",
@@ -33,6 +32,21 @@
     ],
 
     target: {
+        android: {
+            shared_libs: [
+                "libbinder",
+                "libutils",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libbinder",
+                "libutils",
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
         vendor: {
             exclude_shared_libs: ["libvintf"],
         },
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 1418e1f..e286a84 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -275,12 +275,6 @@
 }
 
 prebuilt_etc {
-    name: "android.hardware.telephony.satellite.prebuilt.xml",
-    src: "android.hardware.telephony.satellite.xml",
-    defaults: ["frameworks_native_data_etc_defaults"],
-}
-
-prebuilt_etc {
     name: "android.hardware.thread_network.prebuilt.xml",
     src: "android.hardware.thread_network.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
deleted file mode 100644
index 5966cba..0000000
--- a/data/etc/android.hardware.telephony.satellite.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 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.
--->
-
-<!-- Feature for devices that support Satellite communication via Satellite HAL APIs. -->
-<permissions>
-    <feature name="android.hardware.telephony.satellite" />
-</permissions>
diff --git a/data/etc/android.hardware.type.automotive.xml b/data/etc/android.hardware.type.automotive.xml
index a9b4b05..8605d18 100644
--- a/data/etc/android.hardware.type.automotive.xml
+++ b/data/etc/android.hardware.type.automotive.xml
@@ -17,4 +17,6 @@
 <!-- These features determine that the device running android is a car. -->
 <permissions>
     <feature name="android.hardware.type.automotive" />
+
+    <unavailable-feature name="android.software.picture_in_picture"/>
 </permissions>
diff --git a/data/etc/aosp_excluded_hardware.xml b/data/etc/aosp_excluded_hardware.xml
index c12f435..013f278 100644
--- a/data/etc/aosp_excluded_hardware.xml
+++ b/data/etc/aosp_excluded_hardware.xml
@@ -18,5 +18,4 @@
     <!-- This should be used to exclude this feature from aosp targets. As aosp configurations
     may or may not have a valid location provider -->
     <unavailable-feature name="android.hardware.location.network" />
-    <unavailable-feature name="android.software.device_id_attestation" />
 </permissions>
diff --git a/include/input/Input.h b/include/input/Input.h
index 567361d..bd544b5 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -257,6 +257,10 @@
 
 bool isStylusToolType(ToolType toolType);
 
+struct PointerProperties;
+
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 6c2b313..620c23c 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -118,7 +118,8 @@
     ],
 
     srcs: [
-        "OS.cpp",
+        "OS_android.cpp",
+        "OS_unix_base.cpp",
         "RpcTransportRaw.cpp",
     ],
 
@@ -249,6 +250,7 @@
 
     srcs: [
         // Trusty-specific files
+        "OS_android.cpp",
         "trusty/logging.cpp",
         "trusty/OS.cpp",
         "trusty/RpcServerTrusty.cpp",
@@ -357,6 +359,38 @@
 }
 
 cc_library_static {
+    name: "libbinder_rpc_no_blob",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_BLOB",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
+    name: "libbinder_rpc_no_native_handle",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
     name: "libbinder_rpc_single_threaded",
     defaults: [
         "libbinder_common_defaults",
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index aa67869..f22e90a 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -29,7 +29,6 @@
 #include <binder/Parcel.h>
 #include <binder/RecordedTransaction.h>
 #include <binder/RpcServer.h>
-#include <private/android_filesystem_config.h>
 #include <pthread.h>
 #include <utils/misc.h>
 
@@ -41,10 +40,13 @@
 #endif
 
 #include "BuildFlags.h"
+#include "OS.h"
 #include "RpcState.h"
 
 namespace android {
 
+constexpr uid_t kUidRoot = 0;
+
 // Service implementations inherit from BBinder and IBinder, and this is frozen
 // in prebuilts.
 #ifdef __LP64__
@@ -300,7 +302,7 @@
         return INVALID_OPERATION;
     }
     uid_t uid = IPCThreadState::self()->getCallingUid();
-    if (uid != AID_ROOT) {
+    if (uid != kUidRoot) {
         ALOGE("Binder recording not allowed because client %" PRIu32 " is not root", uid);
         return PERMISSION_DENIED;
     }
@@ -330,7 +332,7 @@
         return INVALID_OPERATION;
     }
     uid_t uid = IPCThreadState::self()->getCallingUid();
-    if (uid != AID_ROOT) {
+    if (uid != kUidRoot) {
         ALOGE("Binder recording not allowed because client %" PRIu32 " is not root", uid);
         return PERMISSION_DENIED;
     }
@@ -634,7 +636,7 @@
         return INVALID_OPERATION;
     }
     uid_t uid = IPCThreadState::self()->getCallingUid();
-    if (uid != AID_ROOT) {
+    if (uid != kUidRoot) {
         ALOGE("%s: not allowed because client %" PRIu32 " is not root", __PRETTY_FUNCTION__, uid);
         return PERMISSION_DENIED;
     }
@@ -794,7 +796,7 @@
         }
 
         case SYSPROPS_TRANSACTION: {
-            report_sysprop_change();
+            if (!binder::os::report_sysprop_change()) return INVALID_OPERATION;
             return NO_ERROR;
         }
 
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index fecae31..8dc1f6a 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -23,7 +23,7 @@
 #include <binder/RpcTransport.h>
 #include <utils/Errors.h>
 
-namespace android {
+namespace android::binder::os {
 
 android::base::Result<void> setNonBlocking(android::base::borrowed_fd fd);
 
@@ -41,4 +41,8 @@
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
 
-} // namespace android
+uint64_t GetThreadId();
+
+bool report_sysprop_change();
+
+} // namespace android::binder::os
diff --git a/libs/binder/OS_android.cpp b/libs/binder/OS_android.cpp
new file mode 100644
index 0000000..ad458eb
--- /dev/null
+++ b/libs/binder/OS_android.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OS.h"
+
+#include <android-base/threads.h>
+#include <utils/misc.h>
+
+namespace android::binder::os {
+
+uint64_t GetThreadId() {
+#ifdef BINDER_RPC_SINGLE_THREADED
+    return 0;
+#else
+    return base::GetThreadId();
+#endif
+}
+
+bool report_sysprop_change() {
+    android::report_sysprop_change();
+    return true;
+}
+
+} // namespace android::binder::os
diff --git a/libs/binder/OS.cpp b/libs/binder/OS_unix_base.cpp
similarity index 98%
rename from libs/binder/OS.cpp
rename to libs/binder/OS_unix_base.cpp
index ce60e33..81933d5 100644
--- a/libs/binder/OS.cpp
+++ b/libs/binder/OS_unix_base.cpp
@@ -24,7 +24,7 @@
 using android::base::ErrnoError;
 using android::base::Result;
 
-namespace android {
+namespace android::binder::os {
 
 // Linux kernel supports up to 253 (from SCM_MAX_FD) for unix sockets.
 constexpr size_t kMaxFdsPerMsg = 253;
@@ -162,4 +162,4 @@
     return TEMP_FAILURE_RETRY(recvmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
 }
 
-} // namespace android
+} // namespace android::binder::os
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 6e3abf5..a3ff7d2 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -40,7 +40,9 @@
 #include <binder/TextOutput.h>
 
 #include <android-base/scopeguard.h>
+#ifndef BINDER_DISABLE_BLOB
 #include <cutils/ashmem.h>
+#endif
 #include <utils/Flattenable.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
@@ -662,7 +664,7 @@
                 // To match kernel binder behavior, we always dup, even if the
                 // FD was unowned in the source parcel.
                 int newFd = -1;
-                if (status_t status = dupFileDescriptor(oldFd, &newFd); status != OK) {
+                if (status_t status = binder::os::dupFileDescriptor(oldFd, &newFd); status != OK) {
                     ALOGW("Failed to duplicate file descriptor %d: %s", oldFd, strerror(-status));
                 }
                 rpcFields->mFds->emplace_back(base::unique_fd(newFd));
@@ -1475,6 +1477,7 @@
     return writeParcelable(*parcelable);
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 status_t Parcel::writeNativeHandle(const native_handle* handle)
 {
     if (!handle || handle->version != sizeof(native_handle))
@@ -1497,6 +1500,7 @@
     err = write(handle->data + handle->numFds, sizeof(int)*handle->numInts);
     return err;
 }
+#endif
 
 status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
     if (auto* rpcFields = maybeRpcFields()) {
@@ -1554,7 +1558,7 @@
 status_t Parcel::writeDupFileDescriptor(int fd)
 {
     int dupFd;
-    if (status_t err = dupFileDescriptor(fd, &dupFd); err != OK) {
+    if (status_t err = binder::os::dupFileDescriptor(fd, &dupFd); err != OK) {
         return err;
     }
     status_t err = writeFileDescriptor(dupFd, true /*takeOwnership*/);
@@ -1573,7 +1577,7 @@
 status_t Parcel::writeDupParcelFileDescriptor(int fd)
 {
     int dupFd;
-    if (status_t err = dupFileDescriptor(fd, &dupFd); err != OK) {
+    if (status_t err = binder::os::dupFileDescriptor(fd, &dupFd); err != OK) {
         return err;
     }
     status_t err = writeParcelFileDescriptor(dupFd, true /*takeOwnership*/);
@@ -1589,6 +1593,12 @@
 
 status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)mutableCopy;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     if (len > INT32_MAX) {
         // don't accept size_t values which may have come from an
         // inadvertent conversion from a negative int.
@@ -1640,6 +1650,7 @@
     }
     ::close(fd);
     return status;
+#endif
 }
 
 status_t Parcel::writeDupImmutableBlobFileDescriptor(int fd)
@@ -2271,6 +2282,7 @@
     return status.exceptionCode();
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 native_handle* Parcel::readNativeHandle() const
 {
     int numFds, numInts;
@@ -2303,6 +2315,7 @@
     }
     return h;
 }
+#endif
 
 int Parcel::readFileDescriptor() const {
     if (const auto* rpcFields = maybeRpcFields()) {
@@ -2386,7 +2399,7 @@
     }
 
     int dupFd;
-    if (status_t err = dupFileDescriptor(got, &dupFd); err != OK) {
+    if (status_t err = binder::os::dupFileDescriptor(got, &dupFd); err != OK) {
         return BAD_VALUE;
     }
 
@@ -2408,7 +2421,7 @@
     }
 
     int dupFd;
-    if (status_t err = dupFileDescriptor(got, &dupFd); err != OK) {
+    if (status_t err = binder::os::dupFileDescriptor(got, &dupFd); err != OK) {
         return BAD_VALUE;
     }
 
@@ -2423,6 +2436,11 @@
 
 status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     int32_t blobType;
     status_t status = readInt32(&blobType);
     if (status) return status;
@@ -2456,6 +2474,7 @@
 
     outBlob->init(fd, ptr, len, isMutable);
     return NO_ERROR;
+#endif
 }
 
 status_t Parcel::read(FlattenableHelperInterface& val) const
@@ -3204,6 +3223,7 @@
     }
 
     size_t openAshmemSize = 0;
+#ifndef BINDER_DISABLE_BLOB
     for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
         const flat_binder_object* flat =
                 reinterpret_cast<const flat_binder_object*>(mData + kernelFields->mObjects[i]);
@@ -3218,6 +3238,7 @@
             }
         }
     }
+#endif
     return openAshmemSize;
 }
 #endif // BINDER_WITH_KERNEL_IPC
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 55fc16d..07ab093 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "RpcServer"
 
 #include <inttypes.h>
+#include <netinet/tcp.h>
 #include <poll.h>
 #include <sys/socket.h>
 #include <sys/un.h>
@@ -24,7 +25,6 @@
 #include <thread>
 #include <vector>
 
-#include <android-base/hex.h>
 #include <android-base/scopeguard.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
@@ -57,7 +57,7 @@
 sp<RpcServer> RpcServer::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
     // Default is without TLS.
     if (rpcTransportCtxFactory == nullptr)
-        rpcTransportCtxFactory = makeDefaultRpcTransportCtxFactory();
+        rpcTransportCtxFactory = binder::os::makeDefaultRpcTransportCtxFactory();
     auto ctx = rpcTransportCtxFactory->newServerCtx();
     if (ctx == nullptr) return nullptr;
     return sp<RpcServer>::make(std::move(ctx));
@@ -216,7 +216,7 @@
     iovec iov{&zero, sizeof(zero)};
     std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
 
-    ssize_t num_bytes = receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
+    ssize_t num_bytes = binder::os::receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
     if (num_bytes < 0) {
         int savedErrno = errno;
         ALOGE("Failed recvmsg: %s", strerror(savedErrno));
@@ -231,7 +231,7 @@
     }
 
     unique_fd fd(std::move(std::get<unique_fd>(fds.back())));
-    if (auto res = setNonBlocking(fd); !res.ok()) {
+    if (auto res = binder::os::setNonBlocking(fd); !res.ok()) {
         ALOGE("Failed setNonBlocking: %s", res.error().message().c_str());
         return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
     }
@@ -484,11 +484,11 @@
                 // don't block if there is some entropy issue
                 if (tries++ > 5) {
                     ALOGE("Cannot find new address: %s",
-                          base::HexString(sessionId.data(), sessionId.size()).c_str());
+                          HexString(sessionId.data(), sessionId.size()).c_str());
                     return;
                 }
 
-                auto status = getRandomBytes(sessionId.data(), sessionId.size());
+                auto status = binder::os::getRandomBytes(sessionId.data(), sessionId.size());
                 if (status != OK) {
                     ALOGE("Failed to read random session ID: %s", strerror(-status));
                     return;
@@ -536,7 +536,7 @@
             auto it = server->mSessions.find(sessionId);
             if (it == server->mSessions.end()) {
                 ALOGE("Cannot add thread, no record of session with ID %s",
-                      base::HexString(sessionId.data(), sessionId.size()).c_str());
+                      HexString(sessionId.data(), sessionId.size()).c_str());
                 return;
             }
             session = it->second;
@@ -572,6 +572,17 @@
         return -savedErrno;
     }
 
+    if (addr.addr()->sa_family == AF_INET || addr.addr()->sa_family == AF_INET6) {
+        int noDelay = 1;
+        int result =
+                setsockopt(socket_fd.get(), IPPROTO_TCP, TCP_NODELAY, &noDelay, sizeof(noDelay));
+        if (result < 0) {
+            int savedErrno = errno;
+            ALOGE("Could not set TCP_NODELAY on  %s", strerror(savedErrno));
+            return -savedErrno;
+        }
+    }
+
     {
         RpcMutexLockGuard _l(mLock);
         if (mServerSocketModifier != nullptr) {
@@ -610,15 +621,14 @@
 void RpcServer::onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) {
     const std::vector<uint8_t>& id = session->mId;
     LOG_ALWAYS_FATAL_IF(id.empty(), "Server sessions must be initialized with ID");
-    LOG_RPC_DETAIL("Dropping session with address %s",
-                   base::HexString(id.data(), id.size()).c_str());
+    LOG_RPC_DETAIL("Dropping session with address %s", HexString(id.data(), id.size()).c_str());
 
     RpcMutexLockGuard _l(mLock);
     auto it = mSessions.find(id);
     LOG_ALWAYS_FATAL_IF(it == mSessions.end(), "Bad state, unknown session id %s",
-                        base::HexString(id.data(), id.size()).c_str());
+                        HexString(id.data(), id.size()).c_str());
     LOG_ALWAYS_FATAL_IF(it->second != session, "Bad state, session has id mismatch %s",
-                        base::HexString(id.data(), id.size()).c_str());
+                        HexString(id.data(), id.size()).c_str());
     (void)mSessions.erase(it);
 }
 
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index c3dee16..fa8f2b5 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -26,7 +26,6 @@
 
 #include <string_view>
 
-#include <android-base/hex.h>
 #include <android-base/macros.h>
 #include <android-base/scopeguard.h>
 #include <binder/BpBinder.h>
@@ -70,7 +69,7 @@
 
 sp<RpcSession> RpcSession::make() {
     // Default is without TLS.
-    return make(makeDefaultRpcTransportCtxFactory());
+    return make(binder::os::makeDefaultRpcTransportCtxFactory());
 }
 
 sp<RpcSession> RpcSession::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
@@ -195,7 +194,7 @@
             fd = request();
             if (!fd.ok()) return BAD_VALUE;
         }
-        if (auto res = setNonBlocking(fd); !res.ok()) {
+        if (auto res = binder::os::setNonBlocking(fd); !res.ok()) {
             ALOGE("setupPreconnectedClient: %s", res.error().message().c_str());
             return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
         }
@@ -310,8 +309,7 @@
     status = state()->getSessionId(connection.get(), sp<RpcSession>::fromExisting(this), &mId);
     if (status != OK) return status;
 
-    LOG_RPC_DETAIL("RpcSession %p has id %s", this,
-                   base::HexString(mId.data(), mId.size()).c_str());
+    LOG_RPC_DETAIL("RpcSession %p has id %s", this, HexString(mId.data(), mId.size()).c_str());
     return OK;
 }
 
@@ -709,7 +707,7 @@
                                                 std::nullopt, nullptr);
         if (sendSessionIdStatus != OK) {
             ALOGE("Could not write session ID ('%s') to socket: %s",
-                  base::HexString(sessionId.data(), sessionId.size()).c_str(),
+                  HexString(sessionId.data(), sessionId.size()).c_str(),
                   statusToString(sendSessionIdStatus).c_str());
             return sendSessionIdStatus;
         }
@@ -770,7 +768,7 @@
     {
         RpcMutexLockGuard _l(mMutex);
         connection->rpcTransport = std::move(rpcTransport);
-        connection->exclusiveTid = rpcGetThreadId();
+        connection->exclusiveTid = binder::os::GetThreadId();
         mConnections.mOutgoing.push_back(connection);
     }
 
@@ -825,7 +823,7 @@
 
     sp<RpcConnection> session = sp<RpcConnection>::make();
     session->rpcTransport = std::move(rpcTransport);
-    session->exclusiveTid = rpcGetThreadId();
+    session->exclusiveTid = binder::os::GetThreadId();
 
     mConnections.mIncoming.push_back(session);
     mConnections.mMaxIncoming = mConnections.mIncoming.size();
@@ -870,7 +868,7 @@
     connection->mConnection = nullptr;
     connection->mReentrant = false;
 
-    uint64_t tid = rpcGetThreadId();
+    uint64_t tid = binder::os::GetThreadId();
     RpcMutexUniqueLock _l(session->mMutex);
 
     session->mConnections.mWaitingThreads++;
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index bac2808..5046253 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -18,7 +18,6 @@
 
 #include "RpcState.h"
 
-#include <android-base/hex.h>
 #include <android-base/macros.h>
 #include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
@@ -363,7 +362,7 @@
     for (int i = 0; i < niovs; i++) {
         LOG_RPC_DETAIL("Sending %s (part %d of %d) on RpcTransport %p: %s",
                        what, i + 1, niovs, connection->rpcTransport.get(),
-                       android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
+                       HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
 
     if (status_t status =
@@ -398,7 +397,7 @@
     for (int i = 0; i < niovs; i++) {
         LOG_RPC_DETAIL("Received %s (part %d of %d) on RpcTransport %p: %s",
                        what, i + 1, niovs, connection->rpcTransport.get(),
-                       android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
+                       HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
     return OK;
 }
@@ -409,10 +408,11 @@
         char codename[PROPERTY_VALUE_MAX];
         property_get("ro.build.version.codename", codename, "");
         if (!strcmp(codename, "REL")) {
-            ALOGE("Cannot use experimental RPC binder protocol on a release branch.");
+            ALOGE("Cannot use experimental RPC binder protocol in a release configuration.");
             return false;
         }
 #else
+        // TODO(b/305983144)
         // don't restrict on other platforms, though experimental should
         // only really be used for testing, we don't have a good way to see
         // what is shipping outside of Android
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index ddbcb57..c089811 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -59,8 +59,8 @@
             override {
         bool sentFds = false;
         auto send = [&](iovec* iovs, int niovs) -> ssize_t {
-            ssize_t ret =
-                    sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
+            ssize_t ret = binder::os::sendMessageOnSocket(mSocket, iovs, niovs,
+                                                          sentFds ? nullptr : ancillaryFds);
             sentFds |= ret > 0;
             return ret;
         };
@@ -73,7 +73,7 @@
             const std::optional<android::base::function_ref<status_t()>>& altPoll,
             std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
         auto recv = [&](iovec* iovs, int niovs) -> ssize_t {
-            return receiveMessageFromSocket(mSocket, iovs, niovs, ancillaryFds);
+            return binder::os::receiveMessageFromSocket(mSocket, iovs, niovs, ancillaryFds);
         };
         return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, recv, "recvmsg", POLLIN,
                                         altPoll);
diff --git a/libs/binder/Utils.cpp b/libs/binder/Utils.cpp
index 0314b0f..47fd17d 100644
--- a/libs/binder/Utils.cpp
+++ b/libs/binder/Utils.cpp
@@ -16,6 +16,7 @@
 
 #include "Utils.h"
 
+#include <android-base/logging.h>
 #include <string.h>
 
 namespace android {
@@ -24,4 +25,22 @@
     memset(data, 0, size);
 }
 
+std::string HexString(const void* bytes, size_t len) {
+    CHECK(bytes != nullptr || len == 0) << bytes << " " << len;
+
+    // b/132916539: Doing this the 'C way', std::setfill triggers ubsan implicit conversion
+    const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
+    const char chars[] = "0123456789abcdef";
+    std::string result;
+    result.resize(len * 2);
+
+    for (size_t i = 0; i < len; i++) {
+        const auto c = bytes8[i];
+        result[2 * i] = chars[c >> 4];
+        result[2 * i + 1] = chars[c & 0xf];
+    }
+
+    return result;
+}
+
 } // namespace android
diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h
index e04199c..dd632c0 100644
--- a/libs/binder/Utils.h
+++ b/libs/binder/Utils.h
@@ -70,4 +70,10 @@
     }
 };
 
+// Converts binary data into a hexString.
+//
+// Hex values are printed in order, e.g. 0xDEAD will result in 'adde' because
+// Android is little-endian.
+std::string HexString(const void* bytes, size_t len);
+
 }   // namespace android
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 45e5ace..98d12bb 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -26,7 +26,9 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 #include <cutils/native_handle.h>
+#endif
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
@@ -324,11 +326,13 @@
     template<typename T>
     status_t            writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")));
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Place a native_handle into the parcel (the native_handle's file-
     // descriptors are dup'ed, so it is safe to delete the native_handle
     // when this function returns).
     // Doesn't take ownership of the native_handle.
     status_t            writeNativeHandle(const native_handle* handle);
+#endif
 
     // Place a file descriptor into the parcel.  The given fd must remain
     // valid for the lifetime of the parcel.
@@ -559,13 +563,14 @@
     // response headers rather than doing it by hand.
     int32_t             readExceptionCode() const;
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Retrieve native_handle from the parcel. This returns a copy of the
     // parcel's native_handle (the caller takes ownership). The caller
-    // must free the native_handle with native_handle_close() and 
+    // must free the native_handle with native_handle_close() and
     // native_handle_delete().
     native_handle*     readNativeHandle() const;
+#endif
 
-    
     // Retrieve a file descriptor from the parcel.  This returns the raw fd
     // in the parcel, which you do not own -- use dup() to get your own copy.
     int                 readFileDescriptor() const;
@@ -1289,6 +1294,7 @@
 
     // Fields only needed when parcelling for "kernel Binder".
     struct KernelFields {
+        KernelFields() {}
         binder_size_t* mObjects = nullptr;
         size_t mObjectsSize = 0;
         size_t mObjectsCapacity = 0;
diff --git a/libs/binder/include/binder/RpcThreads.h b/libs/binder/include/binder/RpcThreads.h
index b80d116..d25f292 100644
--- a/libs/binder/include/binder/RpcThreads.h
+++ b/libs/binder/include/binder/RpcThreads.h
@@ -17,8 +17,6 @@
 
 #include <pthread.h>
 
-#include <android-base/threads.h>
-
 #include <condition_variable>
 #include <functional>
 #include <memory>
@@ -121,10 +119,6 @@
 }
 } // namespace rpc_this_thread
 
-static inline uint64_t rpcGetThreadId() {
-    return 0;
-}
-
 static inline void rpcJoinIfSingleThreaded(RpcMaybeThread& t) {
     t.join();
 }
@@ -136,10 +130,6 @@
 using RpcMaybeThread = std::thread;
 namespace rpc_this_thread = std::this_thread;
 
-static inline uint64_t rpcGetThreadId() {
-    return base::GetThreadId();
-}
-
 static inline void rpcJoinIfSingleThreaded(RpcMaybeThread&) {}
 #endif // BINDER_RPC_SINGLE_THREADED
 
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index f7dd9c9..47da296 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -21,7 +21,9 @@
 #include <android/binder_status.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
+#if __has_include(<private/android_filesystem_config.h>)
 #include <private/android_filesystem_config.h>
+#endif
 
 #include "ibinder_internal.h"
 #include "parcel_internal.h"
@@ -229,7 +231,11 @@
 
         // Shell commands should only be callable by ADB.
         uid_t uid = AIBinder_getCallingUid();
-        if (uid != AID_ROOT && uid != AID_SHELL) {
+        if (uid != 0 /* root */
+#ifdef AID_SHELL
+            && uid != AID_SHELL
+#endif
+        ) {
             if (resultReceiver != nullptr) {
                 resultReceiver->send(-1);
             }
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 76acff5..3ee36cd 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -156,7 +156,10 @@
 }
 
 sp<IFoo> IFoo::getService(const char* instance, AIBinder** outBinder) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binder = AServiceManager_getService(instance);  // maybe nullptr
+#pragma clang diagnostic pop
     if (binder == nullptr) {
         return nullptr;
     }
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 15708ca..cab1a60 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -341,7 +341,10 @@
     // libbinder across processes to the NDK service which doesn't implement
     // shell
     static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> testService = sm->getService(String16(IFoo::kSomeInstanceName));
+#pragma clang diagnostic pop
 
     Vector<String16> argsVec;
     EXPECT_EQ(OK, IBinder::shellCommand(testService, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,
@@ -384,7 +387,10 @@
     // checkService on it, since the other process serving it might not be started yet.
     {
         // getService, not waitForService, to take advantage of timeout
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         auto binder = ndk::SpAIBinder(AServiceManager_getService(IFoo::kSomeInstanceName));
+#pragma clang diagnostic pop
         ASSERT_NE(nullptr, binder.get());
     }
 
@@ -574,7 +580,10 @@
 }
 
 TEST(NdkBinder, RetrieveNonNdkService) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
     ASSERT_NE(nullptr, binder);
     EXPECT_TRUE(AIBinder_isRemote(binder));
     EXPECT_TRUE(AIBinder_isAlive(binder));
@@ -588,7 +597,10 @@
 }
 
 TEST(NdkBinder, LinkToDeath) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
     ASSERT_NE(nullptr, binder);
 
     AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(OnBinderDeath);
@@ -618,7 +630,10 @@
 }
 
 TEST(NdkBinder, SetInheritRtNonLocal) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
     ASSERT_NE(binder, nullptr);
 
     ASSERT_TRUE(AIBinder_isRemote(binder));
@@ -654,11 +669,14 @@
 }
 
 TEST(NdkBinder, EqualityOfRemoteBinderPointer) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binderA = AServiceManager_getService(kExistingNonNdkService);
     ASSERT_NE(nullptr, binderA);
 
     AIBinder* binderB = AServiceManager_getService(kExistingNonNdkService);
     ASSERT_NE(nullptr, binderB);
+#pragma clang diagnostic pop
 
     EXPECT_EQ(binderA, binderB);
 
@@ -672,7 +690,10 @@
 }
 
 TEST(NdkBinder, ABpBinderRefCount) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
     AIBinder_Weak* wBinder = AIBinder_Weak_new(binder);
 
     ASSERT_NE(nullptr, binder);
@@ -695,7 +716,10 @@
 }
 
 TEST(NdkBinder, RequestedSidWorks) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
     std::shared_ptr<aidl::IBinderNdkUnitTest> service =
             aidl::IBinderNdkUnitTest::fromBinder(binder);
 
@@ -718,7 +742,10 @@
 
     std::shared_ptr<MyEmpty> empty = ndk::SharedRefBase::make<MyEmpty>();
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
     std::shared_ptr<aidl::IBinderNdkUnitTest> service =
             aidl::IBinderNdkUnitTest::fromBinder(binder);
 
@@ -741,7 +768,10 @@
 TEST(NdkBinder, ConvertToPlatformBinder) {
     for (const ndk::SpAIBinder& binder :
          {// remote
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
           ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)),
+#pragma clang diagnostic pop
           // local
           ndk::SharedRefBase::make<MyBinderNdkUnitTest>()->asBinder()}) {
         // convert to platform binder
@@ -774,7 +804,10 @@
 TEST(NdkBinder, GetAndVerifyScopedAIBinder_Weak) {
     for (const ndk::SpAIBinder& binder :
          {// remote
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
           ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)),
+#pragma clang diagnostic pop
           // local
           ndk::SharedRefBase::make<MyBinderNdkUnitTest>()->asBinder()}) {
         // get a const ScopedAIBinder_Weak and verify promote
@@ -869,7 +902,10 @@
 
 TEST(NdkBinder, UseHandleShellCommand) {
     static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
 
     EXPECT_EQ("", shellCmdToString(testService, {}));
     EXPECT_EQ("", shellCmdToString(testService, {"", ""}));
@@ -879,7 +915,10 @@
 
 TEST(NdkBinder, FlaggedServiceAccessible) {
     static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestServiceFlagged));
+#pragma clang diagnostic pop
     ASSERT_NE(nullptr, testService);
 }
 
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 463c210..a08cb7a 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -436,7 +436,7 @@
 
 impl<I: FromIBinder + ?Sized> PartialOrd for Strong<I> {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.0.as_binder().partial_cmp(&other.0.as_binder())
+        Some(self.cmp(other))
     }
 }
 
@@ -483,7 +483,7 @@
 
 impl<I: FromIBinder + ?Sized> PartialOrd for Weak<I> {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.weak_binder.partial_cmp(&other.weak_binder)
+        Some(self.cmp(other))
     }
 }
 
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 8841fe6..ed870b6 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -144,6 +144,7 @@
 #[doc(hidden)]
 pub mod unstable_api {
     pub use crate::binder::AsNative;
+    pub use crate::error::status_result;
     pub use crate::proxy::unstable_api::new_spibinder;
     pub use crate::sys::AIBinder;
     pub use crate::sys::AParcel;
diff --git a/libs/binder/rust/tests/serialization.hpp b/libs/binder/rust/tests/serialization.hpp
index 0041608..9edcd6d 100644
--- a/libs/binder/rust/tests/serialization.hpp
+++ b/libs/binder/rust/tests/serialization.hpp
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpragma-once-outside-header"
 #pragma once
+#pragma clang diagnostic pop
 
 #include <binder/IBinder.h>
 
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 8e95d69..5bf9680 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -279,7 +279,10 @@
     while (-1 != (opt = getopt(argc, argv, "gi:"))) {
         switch (opt) {
             case 'g': {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
                 serviceRetriever = &android::IServiceManager::getService;
+#pragma clang diagnostic pop
             } break;
             case 'i': {
                 ip_address = optarg;
diff --git a/libs/binder/tests/binderClearBufTest.cpp b/libs/binder/tests/binderClearBufTest.cpp
index 307151c..e43ee5f 100644
--- a/libs/binder/tests/binderClearBufTest.cpp
+++ b/libs/binder/tests/binderClearBufTest.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/hex.h>
 #include <android-base/logging.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
@@ -24,6 +23,8 @@
 #include <binder/Stability.h>
 #include <gtest/gtest.h>
 
+#include "../Utils.h"
+
 #include <sys/prctl.h>
 #include <thread>
 
@@ -68,13 +69,16 @@
             lastReply = reply.data();
             lastReplySize = reply.dataSize();
         }
-        *outBuffer = android::base::HexString(lastReply, lastReplySize);
+        *outBuffer = android::HexString(lastReply, lastReplySize);
         return result;
     }
 };
 
 TEST(BinderClearBuf, ClearKernelBuffer) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> binder = defaultServiceManager()->getService(kServerName);
+#pragma clang diagnostic pop
     ASSERT_NE(nullptr, binder);
 
     std::string replyBuffer;
diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp
index 77a5fa8..0075688 100644
--- a/libs/binder/tests/binderHostDeviceTest.cpp
+++ b/libs/binder/tests/binderHostDeviceTest.cpp
@@ -135,7 +135,10 @@
 TEST_F(HostDeviceTest, GetService) {
     auto sm = defaultServiceManager();
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     auto rpcBinder = sm->getService(String16(kServiceName));
+#pragma clang diagnostic pop
     ASSERT_NE(nullptr, rpcBinder);
 
     EXPECT_THAT(rpcBinder->pingBinder(), StatusEq(OK));
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 0396869..341e9ce 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -213,7 +213,10 @@
 
             sp<IServiceManager> sm = defaultServiceManager();
             //printf("%s: pid %d, get service\n", __func__, m_pid);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
             m_server = sm->getService(binderLibTestServiceName);
+#pragma clang diagnostic pop
             ASSERT_TRUE(m_server != nullptr);
             //printf("%s: pid %d, get service done\n", __func__, m_pid);
         }
@@ -1107,6 +1110,7 @@
     status_t ret;
     data.writeInterfaceToken(binderLibTestServiceName);
     ret = m_server->transact(BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION, data, &reply);
+    EXPECT_EQ(NO_ERROR, ret);
 
     Parcel data2, reply2;
     status_t ret2;
@@ -1595,9 +1599,8 @@
         }
         switch (code) {
             case BINDER_LIB_TEST_REGISTER_SERVER: {
-                int32_t id;
                 sp<IBinder> binder;
-                id = data.readInt32();
+                /*id =*/data.readInt32();
                 binder = data.readStrongBinder();
                 if (binder == nullptr) {
                     return BAD_VALUE;
@@ -1993,7 +1996,10 @@
         if (index == 0) {
             ret = sm->addService(binderLibTestServiceName, testService);
         } else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
             sp<IBinder> server = sm->getService(binderLibTestServiceName);
+#pragma clang diagnostic pop
             Parcel data, reply;
             data.writeInt32(index);
             data.writeStrongBinder(testService);
diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp
index 6773c95..d08a9bb 100644
--- a/libs/binder/tests/binderRecordReplayTest.cpp
+++ b/libs/binder/tests/binderRecordReplayTest.cpp
@@ -133,7 +133,10 @@
 public:
     void SetUp() override {
         // get the remote service
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         auto binder = defaultServiceManager()->getService(kServerName);
+#pragma clang diagnostic pop
         ASSERT_NE(nullptr, binder);
         mInterface = interface_cast<IBinderRecordReplayTest>(binder);
         mBpBinder = binder->remoteBinder();
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 4c3c68e..1340ea1 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
+#ifndef __ANDROID_VENDOR__
+// only used on NDK tests outside of vendor
 #include <aidl/IBinderRpcTest.h>
+#endif
 #include <android-base/stringprintf.h>
 
 #include <chrono>
@@ -227,7 +230,7 @@
     std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
     fds.emplace_back(std::move(sockServer));
 
-    if (sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
+    if (binder::os::sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
         int savedErrno = errno;
         LOG(FATAL) << "Failed sendMessageOnSocket: " << strerror(savedErrno);
     }
@@ -1589,7 +1592,7 @@
             int buf;
             iovec iov{&buf, sizeof(buf)};
 
-            if (receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
+            if (binder::os::receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
                 int savedErrno = errno;
                 LOG(FATAL) << "Failed receiveMessage: " << strerror(savedErrno);
             }
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index c1364dd..eceff35 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -70,12 +70,23 @@
     return {RpcSecurity::RAW, RpcSecurity::TLS};
 }
 
+static inline bool hasExperimentalRpc() {
+#ifdef __ANDROID__
+    return base::GetProperty("ro.build.version.codename", "") != "REL";
+#else
+    // TODO(b/305983144): restrict on other platforms
+    return true;
+#endif
+}
+
 static inline std::vector<uint32_t> testVersions() {
     std::vector<uint32_t> versions;
     for (size_t i = 0; i < RPC_WIRE_PROTOCOL_VERSION_NEXT; i++) {
         versions.push_back(i);
     }
-    versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+    if (hasExperimentalRpc()) {
+        versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+    }
     return versions;
 }
 
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index e43508e..885bb45 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -50,7 +50,8 @@
 
 TEST(BinderRpc, CanUseExperimentalWireVersion) {
     auto session = RpcSession::make();
-    EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
+    EXPECT_EQ(hasExperimentalRpc(),
+              session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
 }
 
 TEST_P(BinderRpc, Ping) {
diff --git a/libs/binder/tests/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp
index 642cea4..7ec7c99 100644
--- a/libs/binder/tests/binderRpcWireProtocolTest.cpp
+++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/hex.h>
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
@@ -25,6 +24,7 @@
 #include <gtest/gtest.h>
 
 #include "../Debug.h"
+#include "../Utils.h"
 
 namespace android {
 
@@ -176,7 +176,7 @@
         setParcelForRpc(&p, version);
         kFillFuns[i](&p);
 
-        result += base::HexString(p.data(), p.dataSize());
+        result += HexString(p.data(), p.dataSize());
     }
     return result;
 }
@@ -263,16 +263,4 @@
     }
 }
 
-TEST(RpcWire, IfNotExperimentalCodeHasNoExperimentalFeatures) {
-    if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
-        GTEST_SKIP() << "Version is experimental, so experimental features are okay.";
-    }
-
-    // if we set the wire protocol version to experimental, none of the code
-    // should introduce a difference (if this fails, it means we have features
-    // which are enabled under experimental mode, but we aren't actually using
-    // or testing them!)
-    checkRepr(kCurrentRepr, RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
-}
-
 } // namespace android
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 5e8a32a..1c13866 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -605,7 +605,10 @@
     static constexpr const char* getLogTag() { return "SafeInterfaceTest"; }
 
     sp<ISafeInterfaceTest> getRemoteService() {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         sp<IBinder> binder = defaultServiceManager()->getService(kServiceName);
+#pragma clang diagnostic pop
         sp<ISafeInterfaceTest> iface = interface_cast<ISafeInterfaceTest>(binder);
         EXPECT_TRUE(iface != nullptr);
 
diff --git a/libs/binder/tests/binderStabilityTest.cpp b/libs/binder/tests/binderStabilityTest.cpp
index 2398e1e..3d99358 100644
--- a/libs/binder/tests/binderStabilityTest.cpp
+++ b/libs/binder/tests/binderStabilityTest.cpp
@@ -155,7 +155,10 @@
 }
 
 TEST(BinderStability, ForceDowngradeToVendorStability) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer);
+#pragma clang diagnostic pop
     auto server = interface_cast<IBinderStabilityTest>(serverBinder);
 
     ASSERT_NE(nullptr, server.get());
@@ -206,7 +209,10 @@
     EXPECT_EQ(connectionInfo, std::nullopt);
 }
 TEST(BinderStability, CantCallVendorBinderInSystemContext) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sp<IBinder> serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer);
+#pragma clang diagnostic pop
     auto server = interface_cast<IBinderStabilityTest>(serverBinder);
 
     ASSERT_NE(nullptr, server.get());
@@ -310,8 +316,11 @@
 extern "C" void AIBinder_markVendorStability(AIBinder* binder); // <- BAD DO NOT COPY
 
 TEST(BinderStability, NdkCantCallVendorBinderInSystemContext) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     SpAIBinder binder = SpAIBinder(AServiceManager_getService(
         String8(kSystemStabilityServer).c_str()));
+#pragma clang diagnostic pop
 
     std::shared_ptr<aidl::IBinderStabilityTest> remoteServer =
         aidl::IBinderStabilityTest::fromBinder(binder);
diff --git a/libs/binder/tests/binderThroughputTest.cpp b/libs/binder/tests/binderThroughputTest.cpp
index cfaf2a9..0ea4a3f 100644
--- a/libs/binder/tests/binderThroughputTest.cpp
+++ b/libs/binder/tests/binderThroughputTest.cpp
@@ -204,7 +204,10 @@
     for (int i = 0; i < server_count; i++) {
         if (num == i)
             continue;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         workers.push_back(serviceMgr->getService(generateServiceName(i)));
+#pragma clang diagnostic pop
     }
 
     // Run the benchmark if client
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 46d387c..416ffad 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -21,14 +21,15 @@
 #include "parcelables/SingleDataParcelable.h"
 #include "util.h"
 
-#include <android-base/hex.h>
 #include <android/os/IServiceManager.h>
 #include <binder/ParcelableHolder.h>
 #include <binder/PersistableBundle.h>
 #include <binder/Status.h>
 
+#include "../../Utils.h"
+
 using ::android::status_t;
-using ::android::base::HexString;
+using ::android::HexString;
 
 enum ByteEnum : int8_t {};
 enum IntEnum : int32_t {};
diff --git a/libs/binder/tests/parcel_fuzzer/hwbinder.cpp b/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
index 438e8ae..cdc8bcc 100644
--- a/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
@@ -18,12 +18,13 @@
 #include "hwbinder.h"
 #include "util.h"
 
-#include <android-base/hex.h>
 #include <android-base/logging.h>
 #include <hwbinder/Parcel.h>
 
+#include "../../Utils.h"
+
 using ::android::status_t;
-using ::android::base::HexString;
+using ::android::HexString;
 
 // TODO: support scatter-gather types
 
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h
index 5755239..071250d 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h
@@ -15,7 +15,6 @@
  */
 
 #include <android-base/file.h>
-#include <android-base/hex.h>
 #include <android-base/logging.h>
 
 #include <binder/Binder.h>
@@ -27,7 +26,6 @@
 #include <vector>
 
 using android::Parcel;
-using android::base::HexString;
 using std::vector;
 
 namespace android {
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index bef4ab6..5b1e9ea 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -22,7 +22,6 @@
 
 #include <iostream>
 
-#include <android-base/hex.h>
 #include <android-base/logging.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_libbinder.h>
@@ -34,10 +33,12 @@
 #include <sys/resource.h>
 #include <sys/time.h>
 
+#include "../../Utils.h"
+
 using android::fillRandomParcel;
 using android::RandomParcelOptions;
 using android::sp;
-using android::base::HexString;
+using android::HexString;
 
 void fillRandomParcel(::android::hardware::Parcel* p, FuzzedDataProvider&& provider,
                       RandomParcelOptions* options) {
diff --git a/libs/binder/tests/parcel_fuzzer/random_fd.cpp b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
index 7390d49..4a9bd07 100644
--- a/libs/binder/tests/parcel_fuzzer/random_fd.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
@@ -51,7 +51,9 @@
 
                  int flags = O_CLOEXEC;
                  if (provider->ConsumeBool()) flags |= O_DIRECT;
-                 if (provider->ConsumeBool()) flags |= O_NONBLOCK;
+
+                 // TODO(b/236812909): also test blocking
+                 if (true) flags |= O_NONBLOCK;
 
                  CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
 
diff --git a/libs/binder/tests/schd-dbg.cpp b/libs/binder/tests/schd-dbg.cpp
index 0035e4e..d3cd528 100644
--- a/libs/binder/tests/schd-dbg.cpp
+++ b/libs/binder/tests/schd-dbg.cpp
@@ -340,7 +340,10 @@
   for (int i = 0; i < server_count; i++) {
     // self service is in-process so just skip
     if (num == i) continue;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     workers.push_back(serviceMgr->getService(generateServiceName(i)));
+#pragma clang diagnostic pop
   }
 
   // Client for each pair iterates here
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 8ec9823..43e06e0 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -28,7 +28,7 @@
 
 using android::base::Result;
 
-namespace android {
+namespace android::binder::os {
 
 Result<void> setNonBlocking(android::base::borrowed_fd /*fd*/) {
     // Trusty IPC syscalls are all non-blocking by default.
@@ -73,4 +73,4 @@
     return -1;
 }
 
-} // namespace android
+} // namespace android::binder::os
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index ab7a50d..1f05ef7 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -31,28 +31,22 @@
 	$(LIBBINDER_DIR)/FdTrigger.cpp \
 	$(LIBBINDER_DIR)/IInterface.cpp \
 	$(LIBBINDER_DIR)/IResultReceiver.cpp \
+	$(LIBBINDER_DIR)/OS_android.cpp \
 	$(LIBBINDER_DIR)/Parcel.cpp \
 	$(LIBBINDER_DIR)/Stability.cpp \
 	$(LIBBINDER_DIR)/Status.cpp \
 	$(LIBBINDER_DIR)/Utils.cpp \
 	$(LIBBASE_DIR)/hex.cpp \
 	$(LIBBASE_DIR)/stringprintf.cpp \
-	$(LIBUTILS_DIR)/Errors.cpp \
+	$(LIBUTILS_DIR)/binder/Errors.cpp \
+	$(LIBUTILS_DIR)/binder/RefBase.cpp \
+	$(LIBUTILS_DIR)/binder/SharedBuffer.cpp \
+	$(LIBUTILS_DIR)/binder/String16.cpp \
+	$(LIBUTILS_DIR)/binder/String8.cpp \
+	$(LIBUTILS_DIR)/binder/StrongPointer.cpp \
+	$(LIBUTILS_DIR)/binder/Unicode.cpp \
+	$(LIBUTILS_DIR)/binder/VectorImpl.cpp \
 	$(LIBUTILS_DIR)/misc.cpp \
-	$(LIBUTILS_DIR)/RefBase.cpp \
-	$(LIBUTILS_DIR)/StrongPointer.cpp \
-	$(LIBUTILS_DIR)/Unicode.cpp \
-
-# TODO: remove the following when libbinder supports std::string
-# instead of String16 and String8 for Status and descriptors
-MODULE_SRCS += \
-	$(LIBUTILS_DIR)/SharedBuffer.cpp \
-	$(LIBUTILS_DIR)/String16.cpp \
-	$(LIBUTILS_DIR)/String8.cpp \
-
-# TODO: disable dump() transactions to get rid of Vector
-MODULE_SRCS += \
-	$(LIBUTILS_DIR)/VectorImpl.cpp \
 
 MODULE_DEFINES += \
 	LK_DEBUGLEVEL_NO_ALIASES=1 \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 42db29a..2e56cbd 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -35,6 +35,7 @@
 	$(LIBBINDER_DIR)/FdTrigger.cpp \
 	$(LIBBINDER_DIR)/IInterface.cpp \
 	$(LIBBINDER_DIR)/IResultReceiver.cpp \
+	$(LIBBINDER_DIR)/OS_android.cpp \
 	$(LIBBINDER_DIR)/Parcel.cpp \
 	$(LIBBINDER_DIR)/ParcelFileDescriptor.cpp \
 	$(LIBBINDER_DIR)/RpcServer.cpp \
@@ -45,22 +46,15 @@
 	$(LIBBINDER_DIR)/Utils.cpp \
 	$(LIBBASE_DIR)/hex.cpp \
 	$(LIBBASE_DIR)/stringprintf.cpp \
-	$(LIBUTILS_DIR)/Errors.cpp \
+	$(LIBUTILS_DIR)/binder/Errors.cpp \
+	$(LIBUTILS_DIR)/binder/RefBase.cpp \
+	$(LIBUTILS_DIR)/binder/SharedBuffer.cpp \
+	$(LIBUTILS_DIR)/binder/String16.cpp \
+	$(LIBUTILS_DIR)/binder/String8.cpp \
+	$(LIBUTILS_DIR)/binder/StrongPointer.cpp \
+	$(LIBUTILS_DIR)/binder/Unicode.cpp \
+	$(LIBUTILS_DIR)/binder/VectorImpl.cpp \
 	$(LIBUTILS_DIR)/misc.cpp \
-	$(LIBUTILS_DIR)/RefBase.cpp \
-	$(LIBUTILS_DIR)/StrongPointer.cpp \
-	$(LIBUTILS_DIR)/Unicode.cpp \
-
-# TODO: remove the following when libbinder supports std::string
-# instead of String16 and String8 for Status and descriptors
-MODULE_SRCS += \
-	$(LIBUTILS_DIR)/SharedBuffer.cpp \
-	$(LIBUTILS_DIR)/String16.cpp \
-	$(LIBUTILS_DIR)/String8.cpp \
-
-# TODO: disable dump() transactions to get rid of Vector
-MODULE_SRCS += \
-	$(LIBUTILS_DIR)/VectorImpl.cpp \
 
 MODULE_EXPORT_INCLUDES += \
 	$(LOCAL_DIR)/include \
diff --git a/libs/bufferstreams/rust/src/buffers/buffer.rs b/libs/bufferstreams/rust/src/buffers/buffer.rs
new file mode 100644
index 0000000..0a8516e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer.rs
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 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.
+
+//! Wrapper around the HardwareBuffer
+
+use nativewindow::*;
+
+use super::{buffer_owner::NoBufferOwner, BufferOwner};
+
+/// A wrapper for a hardware buffer.
+///
+/// This buffer may be associated with a buffer pool to which it will be returned to it when dropped.
+pub struct Buffer {
+    buffer_owner: Box<dyn BufferOwner>,
+    hardware_buffer: HardwareBuffer,
+}
+
+impl Buffer {
+    /// Create new buffer with a custom [BufferOwner].
+    pub fn new(buffer_owner: Box<dyn BufferOwner>, hardware_buffer: HardwareBuffer) -> Self {
+        Self { buffer_owner, hardware_buffer }
+    }
+
+    /// Create a new buffer with no association to any buffer pool.
+    pub fn new_unowned(hardware_buffer: HardwareBuffer) -> Self {
+        Self { buffer_owner: Box::new(NoBufferOwner), hardware_buffer }
+    }
+
+    /// Get the id of the underlying buffer.
+    pub fn id(&self) -> u64 {
+        self.hardware_buffer.id()
+    }
+
+    /// Get a reference to the underlying hardware buffer.
+    pub fn buffer(&self) -> &HardwareBuffer {
+        &self.hardware_buffer
+    }
+}
+
+impl Drop for Buffer {
+    fn drop(&mut self) {
+        self.buffer_owner.on_return(self);
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    use crate::StreamConfig;
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn test_get_buffer_id() {
+        let hardware_buffer = STREAM_CONFIG.create_hardware_buffer().unwrap();
+        let buffer_id = hardware_buffer.id();
+
+        let buffer = Buffer::new_unowned(hardware_buffer);
+        assert_eq!(buffer_id, buffer.id());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
new file mode 100644
index 0000000..a4abb9d
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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.
+
+use super::Buffer;
+
+/// Trait that represents an owner of a buffer that might need to handle events such as a buffer
+/// being dropped.
+pub trait BufferOwner {
+    /// Called when a buffer is dropped.
+    fn on_return(&self, buffer: &Buffer);
+}
+
+pub(super) struct NoBufferOwner;
+
+impl BufferOwner for NoBufferOwner {
+    fn on_return(&self, _buffer: &Buffer) {}
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_pool.rs b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
new file mode 100644
index 0000000..05804e2
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
@@ -0,0 +1,137 @@
+// Copyright (C) 2023 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.
+
+//! A Buffer Pool containing and managing HardwareBuffers
+
+use std::{
+    collections::HashMap,
+    sync::{Arc, Mutex, Weak},
+};
+
+use nativewindow::*;
+
+use crate::StreamConfig;
+
+use super::{Buffer, BufferOwner};
+
+pub(super) struct BufferPoolInner {
+    size: usize,
+    hardware_buffers: HashMap<u64, HardwareBuffer>,
+    available_buffers: Vec<u64>,
+}
+
+impl BufferPoolInner {
+    pub(super) fn return_buffer(&mut self, buffer_id: u64) {
+        assert!(self.hardware_buffers.contains_key(&buffer_id));
+        assert!(!self.available_buffers.contains(&buffer_id));
+
+        self.available_buffers.push(buffer_id);
+    }
+}
+
+struct BufferPoolOwner(Weak<Mutex<BufferPoolInner>>);
+
+impl BufferOwner for BufferPoolOwner {
+    fn on_return(&self, buffer: &Buffer) {
+        if let Some(locked_buffer_pool) = self.0.upgrade() {
+            let mut buffer_pool = locked_buffer_pool.lock().unwrap();
+
+            buffer_pool.return_buffer(buffer.id());
+        }
+    }
+}
+
+/// A thread-safe collection of buffers.
+///
+/// A buffer pool can be of arbitrary size. It creates and then holds references to all buffers
+/// associated with it.
+pub struct BufferPool(Arc<Mutex<BufferPoolInner>>);
+
+impl BufferPool {
+    /// Creates a new buffer pool of size pool_size. All buffers will be created according to
+    /// the stream config.
+    ///
+    /// This constructor creates all buffers at initialization.
+    pub fn new(pool_size: usize, stream_config: StreamConfig) -> Option<Self> {
+        let mut hardware_buffers = HashMap::new();
+        let mut available_buffers = Vec::new();
+        for _ in 0..pool_size {
+            if let Some(buffer) = stream_config.create_hardware_buffer() {
+                available_buffers.push(buffer.id());
+                hardware_buffers.insert(buffer.id(), buffer);
+            } else {
+                return None;
+            }
+        }
+        Some(Self(Arc::new(Mutex::new(BufferPoolInner {
+            size: pool_size,
+            hardware_buffers,
+            available_buffers,
+        }))))
+    }
+
+    /// Try to acquire the next available buffer in the buffer pool.
+    ///
+    /// If all buffers are in use it will return None.
+    pub fn next_buffer(&mut self) -> Option<Buffer> {
+        let mut inner = self.0.lock().unwrap();
+        if let Some(buffer_id) = inner.available_buffers.pop() {
+            Some(Buffer::new(
+                Box::new(BufferPoolOwner(Arc::downgrade(&self.0))),
+                inner.hardware_buffers[&buffer_id].clone(),
+            ))
+        } else {
+            None
+        }
+    }
+
+    /// Gets the size of the buffer pool.
+    pub fn size(&self) -> usize {
+        let inner = self.0.lock().unwrap();
+        inner.size
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn buffer_pool_next_buffer() {
+        let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+        let next_buffer = buffer_pool.next_buffer();
+
+        assert!(next_buffer.is_some());
+        assert!(buffer_pool.next_buffer().is_none());
+    }
+
+    #[test]
+    fn drop_buffer_returns_to_pool() {
+        let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+        let next_buffer = buffer_pool.next_buffer();
+
+        assert!(next_buffer.is_some());
+        drop(next_buffer);
+        assert!(buffer_pool.next_buffer().is_some());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/mod.rs b/libs/bufferstreams/rust/src/buffers/mod.rs
new file mode 100644
index 0000000..83360d6
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/mod.rs
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 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.
+
+//! Module containing Buffers and BufferPools
+
+mod buffer;
+mod buffer_owner;
+mod buffer_pool;
+
+pub use buffer::*;
+pub use buffer_owner::*;
+pub use buffer_pool::*;
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
index 5964281..be1525d 100644
--- a/libs/bufferstreams/rust/src/lib.rs
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -14,14 +14,15 @@
 
 //! libbufferstreams: Reactive Streams for Graphics Buffers
 
+pub mod buffers;
 pub mod publishers;
 mod stream_config;
 pub mod subscribers;
 pub mod subscriptions;
 
+use buffers::Buffer;
 pub use stream_config::*;
 
-use nativewindow::*;
 use std::time::Instant;
 
 /// This function will print Hello World.
@@ -158,8 +159,8 @@
 
 /// Struct used to contain the buffer.
 pub struct Frame {
-    /// A handle to the C buffer interface.
-    pub buffer: HardwareBuffer,
+    /// A buffer to be used this frame.
+    pub buffer: Buffer,
     /// The time at which the buffer was dispatched.
     pub present_time: Instant,
     /// A fence used for reading/writing safely.
@@ -172,6 +173,8 @@
     use super::*;
 
     use anyhow::anyhow;
+    use buffers::Buffer;
+    use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
     use std::borrow::BorrowMut;
     use std::error::Error;
     use std::ops::Add;
@@ -192,9 +195,11 @@
 
     fn make_frame() -> Frame {
         Frame {
-            buffer: STREAM_CONFIG
-                .create_hardware_buffer()
-                .expect("Unable to create hardware buffer for test"),
+            buffer: Buffer::new_unowned(
+                STREAM_CONFIG
+                    .create_hardware_buffer()
+                    .expect("Unable to create hardware buffer for test"),
+            ),
             present_time: Instant::now() + Duration::from_secs(1),
             fence: 0,
         }
diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
new file mode 100644
index 0000000..846105d
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 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.
+
+//!
+
+use std::time::Instant;
+
+use crate::{
+    buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher,
+    BufferSubscriber, Frame, StreamConfig,
+};
+
+/// The [BufferPoolPublisher] submits buffers from a pool over to the subscriber.
+pub struct BufferPoolPublisher {
+    stream_config: StreamConfig,
+    buffer_pool: BufferPool,
+    subscription: SharedBufferSubscription,
+    subscriber: Option<Box<dyn BufferSubscriber>>,
+}
+
+impl BufferPoolPublisher {
+    /// The [BufferPoolPublisher] needs to initialize a [BufferPool], the [BufferPool] will create
+    /// all buffers at initialization using the stream_config.
+    pub fn new(stream_config: StreamConfig, size: usize) -> Option<Self> {
+        BufferPool::new(size, stream_config).map(|buffer_pool| Self {
+            stream_config,
+            buffer_pool,
+            subscription: SharedBufferSubscription::new(),
+            subscriber: None,
+        })
+    }
+
+    /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from
+    /// [BufferPool] and sent over to the [BufferSubscriber].
+    pub fn send_next_frame(&mut self, present_time: Instant) -> bool {
+        if let Some(subscriber) = self.subscriber.as_mut() {
+            if self.subscription.take_request() {
+                if let Some(buffer) = self.buffer_pool.next_buffer() {
+                    let frame = Frame { buffer, present_time, fence: 0 };
+
+                    subscriber.on_next(frame);
+                    return true;
+                }
+            }
+        }
+        false
+    }
+}
+
+impl BufferPublisher for BufferPoolPublisher {
+    fn get_publisher_stream_config(&self) -> StreamConfig {
+        self.stream_config
+    }
+
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+        assert!(self.subscriber.is_none());
+
+        self.subscriber = Some(Box::new(subscriber));
+        self.subscriber.as_mut().unwrap().on_subscribe(self.subscription.clone_for_subscriber());
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+
+    use super::*;
+
+    use crate::{
+        subscribers::{
+            testing::{TestSubscriber, TestingSubscriberEvent},
+            SharedSubscriber,
+        },
+        StreamConfig,
+    };
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    #[test]
+    fn test_send_next_frame() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+
+        let mut buffer_pool_publisher = BufferPoolPublisher::new(STREAM_CONFIG, 1).unwrap();
+        buffer_pool_publisher.subscribe(subscriber.clone());
+
+        subscriber.map_inner(|s| s.request(1));
+
+        assert!(buffer_pool_publisher.send_next_frame(Instant::now()));
+
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+        assert_eq!(buffer_pool_publisher.subscription.pending_requests(), 0);
+    }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs
index 2fd518e..8ed3ba0 100644
--- a/libs/bufferstreams/rust/src/publishers/mod.rs
+++ b/libs/bufferstreams/rust/src/publishers/mod.rs
@@ -14,4 +14,7 @@
 
 //! This module provides [BufferSubscriber] implementations and helpers.
 
+mod buffer_pool_publisher;
 pub mod testing;
+
+pub use buffer_pool_publisher::*;
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index ffe79a3..29d64af 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -111,12 +111,14 @@
 status_t JankData::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeInt64, frameVsyncId);
     SAFE_PARCEL(output->writeInt32, jankType);
+    SAFE_PARCEL(output->writeInt64, frameIntervalNs);
     return NO_ERROR;
 }
 
 status_t JankData::readFromParcel(const Parcel* input) {
     SAFE_PARCEL(input->readInt64, &frameVsyncId);
     SAFE_PARCEL(input->readInt32, &jankType);
+    SAFE_PARCEL(input->readInt64, &frameIntervalNs);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 613721e..fd8fc8d 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -85,6 +85,7 @@
         changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+        frameRateCategorySmoothSwitchOnly(false),
         frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
@@ -162,6 +163,7 @@
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
     SAFE_PARCEL(output.writeByte, frameRateCategory);
+    SAFE_PARCEL(output.writeBool, frameRateCategorySmoothSwitchOnly);
     SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeBool, autoRefresh);
@@ -296,6 +298,7 @@
     SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
     SAFE_PARCEL(input.readByte, &frameRateCategory);
+    SAFE_PARCEL(input.readBool, &frameRateCategorySmoothSwitchOnly);
     SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
@@ -669,6 +672,7 @@
     if (other.what & eFrameRateCategoryChanged) {
         what |= eFrameRateCategoryChanged;
         frameRateCategory = other.frameRateCategory;
+        frameRateCategorySmoothSwitchOnly = other.frameRateCategorySmoothSwitchOnly;
     }
     if (other.what & eFrameRateSelectionStrategyChanged) {
         what |= eFrameRateSelectionStrategyChanged;
@@ -784,7 +788,8 @@
     CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
                 changeFrameRateStrategy);
-    CHECK_DIFF(diff, eFrameRateCategoryChanged, other, frameRateCategory);
+    CHECK_DIFF2(diff, eFrameRateCategoryChanged, other, frameRateCategory,
+                frameRateCategorySmoothSwitchOnly);
     CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 8a57f92..05e2ddf 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2098,7 +2098,7 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRateCategory(
-        const sp<SurfaceControl>& sc, int8_t category) {
+        const sp<SurfaceControl>& sc, int8_t category, bool smoothSwitchOnly) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -2106,6 +2106,7 @@
     }
     s->what |= layer_state_t::eFrameRateCategoryChanged;
     s->frameRateCategory = category;
+    s->frameRateCategorySmoothSwitchOnly = smoothSwitchOnly;
     return *this;
 }
 
@@ -2604,6 +2605,7 @@
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
         outMode.refreshRate = mode.refreshRate;
+        outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
         outMode.presentationDeadline = mode.presentationDeadline;
@@ -2786,16 +2788,16 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& uids,
+status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& appIds,
                                                          std::vector<float>& thresholds) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(uids, thresholds);
+            ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(appIds, thresholds);
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(uid,
+            ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(appId,
                                                                                       threshold);
     return statusTFromBinderStatus(status);
 }
diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl
index ce30426..b057653 100644
--- a/libs/gui/aidl/android/gui/DisplayMode.aidl
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -29,7 +29,9 @@
     float yDpi = 0.0f;
     int[] supportedHdrTypes;
 
+    // Some modes have peak refresh rate lower than the panel vsync rate.
     float refreshRate = 0.0f;
+    float vsyncRate = 0.0f;
     long appVsyncOffset = 0;
     long sfVsyncOffset = 0;
     long presentationDeadline = 0;
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 507e086..a7cf5dd 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -480,14 +480,14 @@
      */
     void setOverrideFrameRate(int uid, float frameRate);
 
-    oneway void updateSmallAreaDetection(in int[] uids, in float[] thresholds);
+    oneway void updateSmallAreaDetection(in int[] appIds, in float[] thresholds);
 
     /**
-     * Set the small area detection threshold for a specified uid by SmallAreaDetectionController.
-     * Passing the threshold and uid to SurfaceFlinger to update the uid-threshold mapping
+     * Set the small area detection threshold for a specified appId by SmallAreaDetectionController.
+     * Passing the threshold and appId to SurfaceFlinger to update the appId-threshold mapping
      * in the scheduler.
      */
-    oneway void setSmallAreaDetectionThreshold(int uid, float threshold);
+    oneway void setSmallAreaDetectionThreshold(int appId, float threshold);
 
     /**
      * Enables or disables the frame rate overlay in the top left corner.
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 39bcb4a..364a57b 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -120,14 +120,17 @@
     status_t readFromParcel(const Parcel* input) override;
 
     JankData();
-    JankData(int64_t frameVsyncId, int32_t jankType)
-          : frameVsyncId(frameVsyncId), jankType(jankType) {}
+    JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
+          : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
 
     // Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
     int64_t frameVsyncId;
 
     // Bitmask of janks that occurred
     int32_t jankType;
+
+    // Expected duration of the frame
+    nsecs_t frameIntervalNs;
 };
 
 class SurfaceStats : public Parcelable {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 4371007..e1dc791 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -277,8 +277,8 @@
             layer_state_t::eLayerStackChanged;
 
     // Changes that affect the visible region on a display.
-    static constexpr uint64_t VISIBLE_REGION_CHANGES =
-            layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES;
+    static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
+            layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
 
     bool hasValidBuffer() const;
     void sanitize(int32_t permissions);
@@ -361,6 +361,7 @@
 
     // Frame rate category to suggest what frame rate range a surface should run.
     int8_t frameRateCategory;
+    bool frameRateCategorySmoothSwitchOnly;
 
     // Strategy of the layer for frame rate selection.
     int8_t frameRateSelectionStrategy;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 26b1fbd..54c3aa7 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -203,15 +203,15 @@
     // by GameManager.
     static status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
-    // Update the small area detection whole uid-threshold mappings by same size uid and threshold
-    // vector.
+    // Update the small area detection whole appId-threshold mappings by same size appId and
+    // threshold vector.
     // Ref:setSmallAreaDetectionThreshold.
-    static status_t updateSmallAreaDetection(std::vector<int32_t>& uids,
+    static status_t updateSmallAreaDetection(std::vector<int32_t>& appIds,
                                              std::vector<float>& thresholds);
 
-    // Sets the small area detection threshold to particular apps (uid). Passing value 0 means
+    // Sets the small area detection threshold to particular apps (appId). Passing value 0 means
     // to disable small area detection to the app.
-    static status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    static status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     // Switches on/off Auto Low Latency Mode on the connected display. This should only be
     // called if the connected display supports Auto Low Latency Mode as reported by
@@ -686,7 +686,8 @@
         Transaction& setDefaultFrameRateCompatibility(const sp<SurfaceControl>& sc,
                                                       int8_t compatibility);
 
-        Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category);
+        Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category,
+                                          bool smoothSwitchOnly);
 
         Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
 
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 9eee699..e7b1232 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -999,12 +999,12 @@
 
     binder::Status scheduleCommit() override { return binder::Status::ok(); }
 
-    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*uids*/,
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
                                             const std::vector<float>& /*thresholds*/) {
         return binder::Status::ok();
     }
 
-    binder::Status setSmallAreaDetectionThreshold(int32_t /*uid*/, float /*threshold*/) {
+    binder::Status setSmallAreaDetectionThreshold(int32_t /*appId*/, float /*threshold*/) {
         return binder::Status::ok();
     }
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index ab4af1a..69a4f0a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -46,6 +46,14 @@
     name: "aconfig_input_flags_c_lib",
     aconfig_declarations: "aconfig_input_flags",
     host_supported: true,
+    // Use the test version of the aconfig flag library by default to allow tests to set local
+    // overrides for flags, without having to link against a separate version of libinput or of this
+    // library. Bundling this library directly into libinput prevents us from having to add this
+    // library as a shared lib dependency everywhere where libinput is used.
+    test: true,
+    shared: {
+        enabled: false,
+    },
 }
 
 aidl_interface {
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index c127411..bd5b67b 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -217,6 +217,19 @@
     return toolType == ToolType::STYLUS || toolType == ToolType::ERASER;
 }
 
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties) {
+    if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+        return false;
+    }
+    // Need at least one stylus pointer for this event to be considered a stylus event
+    for (const PointerProperties& pointerProperties : properties) {
+        if (isStylusToolType(pointerProperties.toolType)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
     return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
              event.getSource(), event.getDisplayId()},
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 804f96d..705c959 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -180,6 +180,7 @@
 
 bitflags! {
     /// MotionEvent flags.
+    #[derive(Debug)]
     pub struct MotionFlags: u32 {
         /// FLAG_CANCELED
         const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 2d94e70..bbc6d98 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -22,6 +22,49 @@
 use std::collections::HashMap;
 use std::collections::HashSet;
 
+fn verify_event(
+    action: MotionAction,
+    pointer_properties: &[RustPointerProperties],
+    flags: &MotionFlags,
+) -> Result<(), String> {
+    let pointer_count = pointer_properties.len();
+    if pointer_count < 1 {
+        return Err(format!("Invalid {} event: no pointers", action));
+    }
+    match action {
+        MotionAction::Down
+        | MotionAction::HoverEnter
+        | MotionAction::HoverExit
+        | MotionAction::HoverMove
+        | MotionAction::Up => {
+            if pointer_count != 1 {
+                return Err(format!(
+                    "Invalid {} event: there are {} pointers in the event",
+                    action, pointer_count
+                ));
+            }
+        }
+
+        MotionAction::Cancel => {
+            if !flags.contains(MotionFlags::CANCELED) {
+                return Err(format!(
+                    "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
+                    flags
+                ));
+            }
+        }
+
+        MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
+            if action_index >= pointer_count {
+                return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count));
+            }
+        }
+
+        _ => {}
+    }
+    Ok(())
+}
+
 /// The InputVerifier is used to validate a stream of input events.
 pub struct InputVerifier {
     name: String,
@@ -71,15 +114,10 @@
             );
         }
 
+        verify_event(action.into(), pointer_properties, &flags)?;
+
         match action.into() {
             MotionAction::Down => {
-                if pointer_properties.len() != 1 {
-                    return Err(format!(
-                        "{}: Invalid DOWN event: there are {} pointers in the event",
-                        self.name,
-                        pointer_properties.len()
-                    ));
-                }
                 let it = self
                     .touching_pointer_ids_by_device
                     .entry(device_id)
@@ -140,13 +178,6 @@
                 it.remove(&pointer_id);
             }
             MotionAction::Up => {
-                if pointer_properties.len() != 1 {
-                    return Err(format!(
-                        "{}: Invalid UP event: there are {} pointers in the event",
-                        self.name,
-                        pointer_properties.len()
-                    ));
-                }
                 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
                     return Err(format!(
                         "{} Received ACTION_UP but no pointers are currently down for device {:?}",
@@ -171,12 +202,6 @@
                 self.touching_pointer_ids_by_device.remove(&device_id);
             }
             MotionAction::Cancel => {
-                if !flags.contains(MotionFlags::CANCELED) {
-                    return Err(format!(
-                        "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
-                        self.name
-                    ));
-                }
                 if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
                     return Err(format!(
                         "{}: Got ACTION_CANCEL, but the pointers don't match. \
@@ -274,6 +299,26 @@
     use crate::MotionFlags;
     use crate::RustPointerProperties;
     use crate::Source;
+
+    #[test]
+    /**
+     * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
+     */
+    fn bad_down_event() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+        let pointer_properties =
+            Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_err());
+    }
+
     #[test]
     fn single_pointer_stream() {
         let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index dc1575c..90d0a8e 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -19,7 +19,7 @@
 }
 
 rust_bindgen {
-    name: "libnativewindow_bindgen",
+    name: "libnativewindow_bindgen_internal",
     crate_name: "nativewindow_bindgen",
     wrapper_src: "sys/nativewindow_bindings.h",
     source_stem: "bindings",
@@ -28,13 +28,21 @@
         "--bitfield-enum=AHardwareBuffer_UsageFlags",
 
         "--allowlist-file=.*/nativewindow/include/.*\\.h",
+        "--blocklist-type",
+        "AParcel",
+        "--raw-line",
+        "use binder::unstable_api::AParcel;",
 
         "--with-derive-eq",
         "--with-derive-partialeq",
     ],
     shared_libs: [
+        "libbinder_ndk",
         "libnativewindow",
     ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
 
     // Currently necessary for host builds
     // TODO(b/31559095): bionic on host should define this
@@ -44,12 +52,40 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
+
+rust_library {
+    name: "libnativewindow_bindgen",
+    crate_name: "nativewindow_bindgen",
+    srcs: [":libnativewindow_bindgen_internal"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libnativewindow",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+    lints: "none",
+    clippy_lints: "none",
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
     name: "libnativewindow_bindgen_test",
-    srcs: [":libnativewindow_bindgen"],
+    srcs: [":libnativewindow_bindgen_internal"],
     crate_name: "nativewindow_bindgen_test",
+    rustlibs: [
+        "libbinder_rs",
+    ],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     clippy_lints: "none",
@@ -60,6 +96,7 @@
     name: "libnativewindow_defaults",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "libbinder_rs",
         "libnativewindow_bindgen",
     ],
 }
@@ -77,6 +114,7 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 6eb3bbc..6f86c4a 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,13 +16,22 @@
 
 extern crate nativewindow_bindgen as ffi;
 
-pub use ffi::{AHardwareBuffer, AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
+use binder::{
+    binder_impl::{
+        BorrowedParcel, Deserialize, DeserializeArray, DeserializeOption, Serialize,
+        SerializeArray, SerializeOption, NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
+    },
+    unstable_api::{status_result, AsNative},
+    StatusCode,
+};
+use ffi::{AHardwareBuffer, AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel};
 use std::fmt::{self, Debug, Formatter};
 use std::mem::ManuallyDrop;
-use std::ptr::{self, NonNull};
+use std::ptr::{self, null_mut, NonNull};
 
-/// Wrapper around an opaque C AHardwareBuffer.
+/// Wrapper around an opaque C `AHardwareBuffer`.
 #[derive(PartialEq, Eq)]
 pub struct HardwareBuffer(NonNull<AHardwareBuffer>);
 
@@ -120,8 +129,11 @@
     /// Available since API level 31.
     pub fn id(&self) -> u64 {
         let mut out_id = 0;
-        // SAFETY: Neither pointers can be null.
-        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ref(), &mut out_id) };
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it. The id pointer must be valid because it comes from a reference.
+        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ptr(), &mut out_id) };
         assert_eq!(status, 0, "id() failed for AHardwareBuffer with error code: {status}");
 
         out_id
@@ -176,9 +188,10 @@
 
 impl Drop for HardwareBuffer {
     fn drop(&mut self) {
-        // SAFETY: self.0 will never be null. AHardwareBuffers allocated from within Rust will have
-        // a refcount of one, and there is a safety warning on taking an AHardwareBuffer from a raw
-        // pointer requiring callers to ensure the refcount is managed appropriately.
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it.
         unsafe { ffi::AHardwareBuffer_release(self.0.as_ptr()) }
     }
 }
@@ -197,6 +210,82 @@
     }
 }
 
+impl Serialize for HardwareBuffer {
+    fn serialize(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+        SerializeOption::serialize_option(Some(self), parcel)
+    }
+}
+
+impl SerializeOption for HardwareBuffer {
+    fn serialize_option(
+        this: Option<&Self>,
+        parcel: &mut BorrowedParcel,
+    ) -> Result<(), StatusCode> {
+        if let Some(this) = this {
+            parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
+
+            let status =
+            // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+            // because it must have been allocated by `AHardwareBuffer_allocate`,
+            // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+            // released it.
+                unsafe { AHardwareBuffer_writeToParcel(this.0.as_ptr(), parcel.as_native_mut()) };
+            status_result(status)
+        } else {
+            parcel.write(&NULL_PARCELABLE_FLAG)
+        }
+    }
+}
+
+impl Deserialize for HardwareBuffer {
+    type UninitType = Option<Self>;
+
+    fn uninit() -> Option<Self> {
+        None
+    }
+
+    fn from_init(value: Self) -> Option<Self> {
+        Some(value)
+    }
+
+    fn deserialize(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+        DeserializeOption::deserialize_option(parcel)
+            .transpose()
+            .unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
+    }
+}
+
+impl DeserializeOption for HardwareBuffer {
+    fn deserialize_option(parcel: &BorrowedParcel) -> Result<Option<Self>, StatusCode> {
+        let present: i32 = parcel.read()?;
+        match present {
+            NULL_PARCELABLE_FLAG => Ok(None),
+            NON_NULL_PARCELABLE_FLAG => {
+                let mut buffer = null_mut();
+
+                let status =
+                // SAFETY: Both pointers must be valid because they are obtained from references.
+                // `AHardwareBuffer_readFromParcel` doesn't store them or do anything else special
+                // with them. If it returns success then it will have allocated a new
+                // `AHardwareBuffer` and incremented the reference count, so we can use it until we
+                // release it.
+                    unsafe { AHardwareBuffer_readFromParcel(parcel.as_native(), &mut buffer) };
+
+                status_result(status)?;
+
+                Ok(Some(Self(NonNull::new(buffer).expect(
+                    "AHardwareBuffer_readFromParcel returned success but didn't allocate buffer",
+                ))))
+            }
+            _ => Err(StatusCode::BAD_VALUE),
+        }
+    }
+}
+
+impl SerializeArray for HardwareBuffer {}
+
+impl DeserializeArray for HardwareBuffer {}
+
 // SAFETY: The underlying *AHardwareBuffers can be moved between threads.
 unsafe impl Send for HardwareBuffer {}
 
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index e652aee..4525a42 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -16,5 +16,6 @@
 
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
+#include <android/hardware_buffer_aidl.h>
 #include <android/hdr_metadata.h>
 #include <android/native_window.h>
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index b992d82..818d035 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -107,7 +107,7 @@
     // This interface, while still in use until a suitable replacement is built,
     // should be considered deprecated, minus some methods which still may be
     // used to support legacy behavior.
-    virtual std::future<void> primeCache() = 0;
+    virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
 
     // dump the extension strings. always call the base class.
     virtual void dump(std::string& result) = 0;
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index 160006d..a58a65c 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -33,7 +33,7 @@
     RenderEngine();
     ~RenderEngine() override;
 
-    MOCK_METHOD0(primeCache, std::future<void>());
+    MOCK_METHOD1(primeCache, std::future<void>(bool));
     MOCK_METHOD1(dump, void(std::string&));
     MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
     MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 9d61d62..abe0d9b 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -17,6 +17,7 @@
 #include "AutoBackendTexture.h"
 #include "SkiaRenderEngine.h"
 #include "android-base/unique_fd.h"
+#include "cutils/properties.h"
 #include "renderengine/DisplaySettings.h"
 #include "renderengine/LayerSettings.h"
 #include "renderengine/impl/ExternalTexture.h"
@@ -49,6 +50,15 @@
 // a color correction effect is added to the shader.
 constexpr auto kDestDataSpace = ui::Dataspace::SRGB;
 constexpr auto kOtherDataSpace = ui::Dataspace::DISPLAY_P3;
+constexpr auto kBT2020DataSpace = ui::Dataspace::BT2020_ITU_PQ;
+constexpr auto kExtendedHdrDataSpce =
+        static_cast<ui::Dataspace>(ui::Dataspace::RANGE_EXTENDED | ui::Dataspace::TRANSFER_SRGB |
+                                   ui::Dataspace::STANDARD_DCI_P3);
+// Dimming is needed to trigger linear effects for some dataspace pairs
+const std::array<float, 3> kLayerWhitePoints = {
+        1000.0f, 500.0f,
+        100.0f, // trigger dithering by dimming below 20%
+};
 } // namespace
 
 static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -317,6 +327,298 @@
     renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
 }
 
+static void drawImageDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture,
+                                  const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // 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},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 1.f,
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    std::vector<LayerSettings> layers;
+
+    for (auto layerWhitePoint : kLayerWhitePoints) {
+        layer.whitePointNits = layerWhitePoint;
+        layers.push_back(layer);
+    }
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawTransparentImageDimmedLayers(SkiaRenderEngine* renderengine,
+                                             const DisplaySettings& display,
+                                             const std::shared_ptr<ExternalTexture>& dstTexture,
+                                             const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = mat4(),
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = false,
+                                          }},
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    for (auto roundedCornerRadius : {0.f, 50.f}) {
+        layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+        for (auto alpha : {0.5f, 1.0f}) {
+            layer.alpha = alpha;
+            for (auto isOpaque : {true, false}) {
+                if (roundedCornerRadius == 0.f && isOpaque) {
+                    // already covered in drawImageDimmedLayers
+                    continue;
+                }
+
+                layer.source.buffer.isOpaque = isOpaque;
+                std::vector<LayerSettings> layers;
+
+                for (auto layerWhitePoint : kLayerWhitePoints) {
+                    layer.whitePointNits = layerWhitePoint;
+                    layers.push_back(layer);
+                }
+                renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+            }
+        }
+    }
+}
+
+static void drawClippedDimmedImageLayers(SkiaRenderEngine* renderengine,
+                                         const DisplaySettings& display,
+                                         const std::shared_ptr<ExternalTexture>& dstTexture,
+                                         const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+
+    // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+    // blending instead of EllipticalRRect, so enlarge them a bit.
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    FloatRect boundary(0, 0, displayRect.width(),
+                       displayRect.height() - 20); // boundary is smaller
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = mat4(),
+                            .boundaries = boundary,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {27.f, 27.f},
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = false,
+                                          }},
+            .alpha = 1.f,
+            .sourceDataspace = kDestDataSpace,
+    };
+
+    std::array<mat4, 2> transforms = {kScaleAndTranslate, kScaleAsymmetric};
+
+    constexpr float radius = 27.f;
+
+    for (size_t i = 0; i < transforms.size(); i++) {
+        layer.geometry.positionTransform = transforms[i];
+        layer.geometry.roundedCornersRadius = {radius, radius};
+
+        std::vector<LayerSettings> layers;
+
+        for (auto layerWhitePoint : kLayerWhitePoints) {
+            layer.whitePointNits = layerWhitePoint;
+            layers.push_back(layer);
+        }
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+
+static void drawSolidDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = rect,
+                            .roundedCornersCrop = rect,
+                    },
+            .source =
+                    PixelSource{
+                            .solidColor = half3(0.1f, 0.2f, 0.3f),
+                    },
+            .alpha = 1.f,
+    };
+
+    std::vector<LayerSettings> layers;
+
+    for (auto layerWhitePoint : kLayerWhitePoints) {
+        layer.whitePointNits = layerWhitePoint;
+        layers.push_back(layer);
+    }
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawBT2020ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                  const std::shared_ptr<ExternalTexture>& dstTexture,
+                                  const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // 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},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 1.f,
+            .sourceDataspace = kBT2020DataSpace,
+    };
+
+    for (auto alpha : {0.5f, 1.f}) {
+        layer.alpha = alpha;
+        std::vector<LayerSettings> layers;
+        layer.whitePointNits = -1.f;
+        layers.push_back(layer);
+
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+static void drawBT2020ClippedImageLayers(SkiaRenderEngine* renderengine,
+                                         const DisplaySettings& display,
+                                         const std::shared_ptr<ExternalTexture>& dstTexture,
+                                         const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+
+    // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+    // blending instead of EllipticalRRect, so enlarge them a bit.
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    FloatRect boundary(0, 0, displayRect.width(),
+                       displayRect.height() - 10); // boundary is smaller
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .positionTransform = kScaleAsymmetric,
+                            .boundaries = boundary,
+                            .roundedCornersCrop = rect,
+                            .roundedCornersRadius = {64.1f, 64.1f},
+                    },
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .maxLuminanceNits = 1000.f,
+                                                  .usePremultipliedAlpha = true,
+                                                  .isOpaque = true,
+                                          }},
+            .alpha = 0.5f,
+            .sourceDataspace = kBT2020DataSpace,
+    };
+
+    std::vector<LayerSettings> layers = {layer};
+    renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawExtendedHDRImageLayers(SkiaRenderEngine* renderengine,
+                                       const DisplaySettings& display,
+                                       const std::shared_ptr<ExternalTexture>& dstTexture,
+                                       const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // 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},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = true}},
+            .alpha = 0.5f,
+            .sourceDataspace = kExtendedHdrDataSpce,
+    };
+
+    for (auto roundedCornerRadius : {0.f, 50.f}) {
+        layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+        for (auto alpha : {0.5f, 1.f}) {
+            layer.alpha = alpha;
+            std::vector<LayerSettings> layers;
+
+            for (auto layerWhitePoint : kLayerWhitePoints) {
+                layer.whitePointNits = layerWhitePoint;
+                layers.push_back(layer);
+            }
+            renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+        }
+    }
+}
+
+static void drawP3ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                              const std::shared_ptr<ExternalTexture>& dstTexture,
+                              const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            // 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},
+                    },
+            .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+                                                   .maxLuminanceNits = 1000.f,
+                                                   .usePremultipliedAlpha = true,
+                                                   .isOpaque = false}},
+            .alpha = 0.5f,
+            .sourceDataspace = kOtherDataSpace,
+    };
+
+    for (auto alpha : {0.5f, 1.f}) {
+        layer.alpha = alpha;
+        std::vector<LayerSettings> layers;
+
+        for (auto layerWhitePoint : kLayerWhitePoints) {
+            layer.whitePointNits = layerWhitePoint;
+            layers.push_back(layer);
+        }
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+
 //
 // The collection of shaders cached here were found by using perfetto to record shader compiles
 // during actions that involve RenderEngine, logging the layer settings, and the shader code
@@ -328,7 +630,7 @@
 //    kFlushAfterEveryLayer = true
 // in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
 //    gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
     const int previousCount = renderengine->reportShadersCompiled();
     if (previousCount) {
         ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -353,6 +655,23 @@
                 .maxLuminance = 500,
                 .outputDataspace = kOtherDataSpace,
         };
+        DisplaySettings p3DisplayEnhance{.physicalDisplay = displayRect,
+                                         .clip = displayRect,
+                                         .maxLuminance = 500,
+                                         .outputDataspace = kOtherDataSpace,
+                                         .dimmingStage = aidl::android::hardware::graphics::
+                                                 composer3::DimmingStage::GAMMA_OETF,
+                                         .renderIntent = aidl::android::hardware::graphics::
+                                                 composer3::RenderIntent::ENHANCE};
+        DisplaySettings bt2020Display{.physicalDisplay = displayRect,
+                                      .clip = displayRect,
+                                      .maxLuminance = 500,
+                                      .outputDataspace = ui::Dataspace::BT2020,
+                                      .deviceHandlesColorTransform = true,
+                                      .dimmingStage = aidl::android::hardware::graphics::composer3::
+                                              DimmingStage::GAMMA_OETF,
+                                      .renderIntent = aidl::android::hardware::graphics::composer3::
+                                              RenderIntent::TONE_MAP_ENHANCE};
 
         const int64_t usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
 
@@ -377,6 +696,8 @@
                                                impl::ExternalTexture::Usage::WRITEABLE);
         drawHolePunchLayer(renderengine, display, dstTexture);
         drawSolidLayers(renderengine, display, dstTexture);
+        drawSolidLayers(renderengine, p3Display, dstTexture);
+        drawSolidDimmedLayers(renderengine, display, dstTexture);
 
         drawShadowLayers(renderengine, display, srcTexture);
         drawShadowLayers(renderengine, p3Display, srcTexture);
@@ -417,12 +738,38 @@
 
         for (auto texture : textures) {
             drawImageLayers(renderengine, display, dstTexture, texture);
+
+            drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+            drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+            drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+
             // Draw layers for b/185569240.
             drawClippedLayers(renderengine, display, dstTexture, texture);
         }
 
         drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
 
+        drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+        drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+                                         externalTexture);
+
+        drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+        if (shouldPrimeUltraHDR) {
+            drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+            drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+            drawBT2020ImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+
+            drawExtendedHDRImageLayers(renderengine, display, dstTexture, externalTexture);
+            drawExtendedHDRImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+            drawExtendedHDRImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+
+            drawP3ImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+        }
+
         // draw one final layer synchronously to force GL submit
         LayerSettings layer{
                 .source = PixelSource{.solidColor = half3(0.f, 0.f, 0.f)},
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 437571e..62f6705 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -22,7 +22,7 @@
 
 class Cache {
 public:
-    static void primeShaderCache(SkiaRenderEngine*);
+    static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
 
 private:
     Cache() = default;
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 7ec98ea..3729be6 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -243,8 +243,8 @@
 
 using base::StringAppendF;
 
-std::future<void> SkiaRenderEngine::primeCache() {
-    Cache::primeShaderCache(this);
+std::future<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
+    Cache::primeShaderCache(this, shouldPrimeUltraHDR);
     return {};
 }
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 3db0c1b..ac134af 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -62,7 +62,7 @@
     SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur);
     ~SkiaRenderEngine() override;
 
-    std::future<void> primeCache() override final;
+    std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
     void cleanupPostRender() override final;
     bool supportsBackgroundBlur() override final {
         return mBlurFilter != nullptr;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 17f263d..8821c0e 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -25,6 +25,7 @@
 #include <GrContextOptions.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 
 #include <android-base/stringprintf.h>
 #include <gui/TraceUtils.h>
@@ -603,11 +604,11 @@
     sSetupVulkanInterface();
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+    contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
     if (supportsProtectedContentImpl()) {
         contexts.second =
-                GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
-                                            options);
+                GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
+                                             options);
     }
 
     return contexts;
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1..5bf6560 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
 
 #include "SkiaMemoryReporter.h"
 
-#include <SkString.h>
 #include <android-base/stringprintf.h>
 #include <log/log_main.h>
 
@@ -142,7 +141,7 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 StringAppendF(&log, "  %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
-                              traceValue.value, traceValue.units, traceValue.count, entry);
+                              traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
             }
             if (mItemize) {
                 for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@
                         auto result = individualItem.second.find("size");
                         TraceValue size = convertUnits(result->second);
                         StringAppendF(&log, "    %s: size[%.2f %s]", individualItem.first.c_str(),
-                                      size.value, size.units);
+                                      size.value, size.units.c_str());
                         if (!wrappedResources) {
                             for (const auto& itemValues : individualItem.second) {
                                 if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@
                                 TraceValue traceValue = convertUnits(itemValues.second);
                                 if (traceValue.value == 0.0f) {
                                     StringAppendF(&log, " %s[%s]", itemValues.first,
-                                                  traceValue.units);
+                                                  traceValue.units.c_str());
                                 } else {
                                     StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
-                                                  traceValue.value, traceValue.units);
+                                                  traceValue.value, traceValue.units.c_str());
                                 }
                             }
                         }
@@ -184,16 +183,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                  total.value, total.units, purgeable.value, purgeable.units);
+                  total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("bytes") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("KB") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b..da91674 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
 #include <string>
@@ -75,7 +76,7 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        SkString units;
         float value;
         int count;
     };
@@ -104,4 +105,4 @@
 
 } /* namespace skia */
 } /* namespace renderengine */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 6023808..11d4fde 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -3204,7 +3204,7 @@
     }
     initializeRenderEngine();
 
-    auto fut = mRE->primeCache();
+    auto fut = mRE->primeCache(false);
     if (fut.valid()) {
         fut.wait();
     }
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 7289fe7..1b9adba 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -50,8 +50,8 @@
 }
 
 TEST_F(RenderEngineThreadedTest, primeCache) {
-    EXPECT_CALL(*mRenderEngine, primeCache());
-    mThreadedRE->primeCache();
+    EXPECT_CALL(*mRenderEngine, primeCache(false));
+    mThreadedRE->primeCache(false);
     // need to call ANY synchronous function after primeCache to ensure that primeCache has
     // completed asynchronously before the test completes execution.
     mThreadedRE->getContextPriority();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 786a6fe..367bee8 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -129,7 +129,7 @@
     mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
 }
 
-std::future<void> RenderEngineThreaded::primeCache() {
+std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
     const auto resultPromise = std::make_shared<std::promise<void>>();
     std::future<void> resultFuture = resultPromise->get_future();
     ATRACE_CALL();
@@ -137,19 +137,20 @@
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([resultPromise](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::primeCache");
-            if (setSchedFifo(false) != NO_ERROR) {
-                ALOGW("Couldn't set SCHED_OTHER for primeCache");
-            }
+        mFunctionCalls.push(
+                [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
+                    ATRACE_NAME("REThreaded::primeCache");
+                    if (setSchedFifo(false) != NO_ERROR) {
+                        ALOGW("Couldn't set SCHED_OTHER for primeCache");
+                    }
 
-            instance.primeCache();
-            resultPromise->set_value();
+                    instance.primeCache(shouldPrimeUltraHDR);
+                    resultPromise->set_value();
 
-            if (setSchedFifo(true) != NO_ERROR) {
-                ALOGW("Couldn't set SCHED_FIFO for primeCache");
-            }
-        });
+                    if (setSchedFifo(true) != NO_ERROR) {
+                        ALOGW("Couldn't set SCHED_FIFO for primeCache");
+                    }
+                });
     }
     mCondition.notify_one();
 
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 1093f5f..74af2bd 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -42,7 +42,7 @@
 
     RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
     ~RenderEngineThreaded() override;
-    std::future<void> primeCache() override;
+    std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
 
     void dump(std::string& result) override;
 
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index b6b9cc4..d992aa5 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -49,6 +49,7 @@
         "liblog",
         "libhardware",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
     ],
 
     export_include_dirs: ["include"],
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 634d35a..12f600e 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -43,6 +43,8 @@
     CREATE_SENSOR_DIRECT_CONNECTION,
     SET_OPERATION_PARAMETER,
     GET_RUNTIME_SENSOR_LIST,
+    ENABLE_REPLAY_DATA_INJECTION,
+    ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION,
 };
 
 class BpSensorServer : public BpInterface<ISensorServer>
@@ -162,6 +164,20 @@
         return reply.readInt32();
     }
 
+    virtual int isReplayDataInjectionEnabled() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+        remote()->transact(ENABLE_REPLAY_DATA_INJECTION, data, &reply);
+        return reply.readInt32();
+    }
+
+    virtual int isHalBypassReplayDataInjectionEnabled() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+        remote()->transact(ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION, data, &reply);
+        return reply.readInt32();
+    }
+
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
             const native_handle_t *resource) {
@@ -237,6 +253,18 @@
             reply->writeInt32(static_cast<int32_t>(ret));
             return NO_ERROR;
         }
+        case ENABLE_REPLAY_DATA_INJECTION: {
+            CHECK_INTERFACE(ISensorServer, data, reply);
+            int32_t ret = isReplayDataInjectionEnabled();
+            reply->writeInt32(static_cast<int32_t>(ret));
+            return NO_ERROR;
+        }
+        case ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION: {
+            CHECK_INTERFACE(ISensorServer, data, reply);
+            int32_t ret = isHalBypassReplayDataInjectionEnabled();
+            reply->writeInt32(static_cast<int32_t>(ret));
+            return NO_ERROR;
+        }
         case GET_DYNAMIC_SENSOR_LIST: {
             CHECK_INTERFACE(ISensorServer, data, reply);
             const String16& opPackageName = data.readString16();
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 980f8d1..d112a12 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -26,6 +26,8 @@
 #include <utils/RefBase.h>
 #include <utils/Singleton.h>
 
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
+
 #include <binder/IBinder.h>
 #include <binder/IPermissionController.h>
 #include <binder/IServiceManager.h>
@@ -39,6 +41,43 @@
 namespace android {
 // ----------------------------------------------------------------------------
 
+namespace {
+
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
+
+static constexpr int DEVICE_ID_DEFAULT = 0;
+
+// Returns the deviceId of the device where this uid is observed. If the uid is present on more than
+// one devices, return the default deviceId.
+int getDeviceIdForUid(uid_t uid) {
+    sp<IBinder> binder =
+            defaultServiceManager()->checkService(String16("virtualdevice_native"));
+    if (binder != nullptr) {
+        auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+        std::vector<int> deviceIds;
+        vdm->getDeviceIdsForUid(uid, &deviceIds);
+        // If the UID is associated with multiple virtual devices, use the default device's
+        // sensors as we cannot disambiguate here. This effectively means that the app has
+        // activities on different devices at the same time, so it must handle the device
+        // awareness by itself.
+        if (deviceIds.size() == 1) {
+            const int deviceId = deviceIds.at(0);
+            int devicePolicy = IVirtualDeviceManagerNative::DEVICE_POLICY_DEFAULT;
+            vdm->getDevicePolicy(deviceId,
+                                 IVirtualDeviceManagerNative::POLICY_TYPE_SENSORS,
+                                 &devicePolicy);
+            if (devicePolicy == IVirtualDeviceManagerNative::DEVICE_POLICY_CUSTOM) {
+                return deviceId;
+            }
+        }
+    } else {
+        ALOGW("Cannot get virtualdevice_native service");
+    }
+    return DEVICE_ID_DEFAULT;
+}
+
+}  // namespace
+
 Mutex SensorManager::sLock;
 std::map<String16, SensorManager*> SensorManager::sPackageInstances;
 
@@ -53,6 +92,7 @@
         sensorManager = iterator->second;
     } else {
         String16 opPackageName = packageName;
+        const uid_t uid = IPCThreadState::self()->getCallingUid();
 
         // It is possible that the calling code has no access to the package name.
         // In this case we will get the packages for the calling UID and pick the
@@ -63,7 +103,6 @@
         if (opPackageName.size() <= 0) {
             sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
             if (binder != nullptr) {
-                const uid_t uid = IPCThreadState::self()->getCallingUid();
                 Vector<String16> packages;
                 interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
                 if (!packages.isEmpty()) {
@@ -76,7 +115,10 @@
             }
         }
 
-        sensorManager = new SensorManager(opPackageName);
+        // Check if the calling UID is observed on a virtual device. If so, provide that device's
+        // sensors by default instead of the default device's sensors.
+        const int deviceId = getDeviceIdForUid(uid);
+        sensorManager = new SensorManager(opPackageName, deviceId);
 
         // If we had no package name, we looked it up from the UID and the sensor
         // manager instance we created should also be mapped to the empty package
@@ -102,8 +144,9 @@
     }
 }
 
-SensorManager::SensorManager(const String16& opPackageName)
-    : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
+SensorManager::SensorManager(const String16& opPackageName, int deviceId)
+    : mSensorList(nullptr), mOpPackageName(opPackageName), mDeviceId(deviceId),
+        mDirectConnectionHandle(1) {
     Mutex::Autolock _l(mLock);
     assertStateLocked();
 }
@@ -174,7 +217,12 @@
         mDeathObserver = new DeathObserver(*const_cast<SensorManager *>(this));
         IInterface::asBinder(mSensorServer)->linkToDeath(mDeathObserver);
 
-        mSensors = mSensorServer->getSensorList(mOpPackageName);
+        if (mDeviceId == DEVICE_ID_DEFAULT) {
+            mSensors = mSensorServer->getSensorList(mOpPackageName);
+        } else {
+            mSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, mDeviceId);
+        }
+
         size_t count = mSensors.size();
         // If count is 0, mSensorList will be non-null. This is old
         // existing behavior and callers expect this.
@@ -310,6 +358,22 @@
     return false;
 }
 
+bool SensorManager::isReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    if (assertStateLocked() == NO_ERROR) {
+        return mSensorServer->isReplayDataInjectionEnabled();
+    }
+    return false;
+}
+
+bool SensorManager::isHalBypassReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    if (assertStateLocked() == NO_ERROR) {
+        return mSensorServer->isHalBypassReplayDataInjectionEnabled();
+    }
+    return false;
+}
+
 int SensorManager::createDirectChannel(
         size_t size, int channelType, const native_handle_t *resourceHandle) {
     static constexpr int DEFAULT_DEVICE_ID = 0;
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index 5815728..00bc1d6 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -48,6 +48,8 @@
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
              int mode, const String16& opPackageName, const String16& attributionTag) = 0;
     virtual int32_t isDataInjectionEnabled() = 0;
+    virtual int32_t isReplayDataInjectionEnabled() = 0;
+    virtual int32_t isHalBypassReplayDataInjectionEnabled() = 0;
 
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index bb44cb8..e67fac7 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -65,6 +65,8 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    bool isReplayDataInjectionEnabled();
+    bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
     int createDirectChannel(
         int deviceId, size_t size, int channelType, const native_handle_t *channelData);
@@ -77,7 +79,7 @@
     void sensorManagerDied();
     static status_t waitForSensorService(sp<ISensorServer> *server);
 
-    explicit SensorManager(const String16& opPackageName);
+    explicit SensorManager(const String16& opPackageName, int deviceId);
     status_t assertStateLocked();
 
 private:
@@ -92,6 +94,7 @@
     Vector<Sensor> mDynamicSensors;
     sp<IBinder::DeathRecipient> mDeathObserver;
     const String16 mOpPackageName;
+    const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
     int32_t mDirectConnectionHandle;
 };
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index 65a8769..a469c78 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -37,7 +37,9 @@
     float yDpi = 0;
     std::vector<ui::Hdr> supportedHdrTypes;
 
-    float refreshRate = 0;
+    // Some modes have peak refresh rate lower than the panel vsync rate.
+    float refreshRate = 0.f;
+    float vsyncRate = 0.f;
     nsecs_t appVsyncOffset = 0;
     nsecs_t sfVsyncOffset = 0;
     nsecs_t presentationDeadline = 0;
diff --git a/libs/vibrator/OWNERS b/libs/vibrator/OWNERS
index d073e2b..c4de58a 100644
--- a/libs/vibrator/OWNERS
+++ b/libs/vibrator/OWNERS
@@ -1 +1,2 @@
+# Bug component: 345036
 include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index ee1f3cb..6f092a6 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -39,6 +39,9 @@
       "options": [
         {
           "include-filter": "android.hardware.input.cts.tests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     },
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index fa8f548..30e6802 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -215,8 +215,8 @@
                          int32_t buttonState, MotionClassification classification,
                          int32_t edgeFlags, float xPrecision, float yPrecision,
                          float xCursorPosition, float yCursorPosition, nsecs_t downTime,
-                         uint32_t pointerCount, const PointerProperties* pointerProperties,
-                         const PointerCoords* pointerCoords)
+                         const std::vector<PointerProperties>& pointerProperties,
+                         const std::vector<PointerCoords>& pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
@@ -233,12 +233,8 @@
         xCursorPosition(xCursorPosition),
         yCursorPosition(yCursorPosition),
         downTime(downTime),
-        pointerCount(pointerCount) {
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        this->pointerProperties[i] = pointerProperties[i];
-        this->pointerCoords[i] = pointerCoords[i];
-    }
-}
+        pointerProperties(pointerProperties),
+        pointerCoords(pointerCoords) {}
 
 MotionEntry::~MotionEntry() {}
 
@@ -258,7 +254,7 @@
                         buttonState, motionClassificationToString(classification), edgeFlags,
                         xPrecision, yPrecision, xCursorPosition, yCursorPosition);
 
-    for (uint32_t i = 0; i < pointerCount; i++) {
+    for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (i) {
             msg += ", ";
         }
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 98e2507..b341784 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -175,16 +175,18 @@
     float xCursorPosition;
     float yCursorPosition;
     nsecs_t downTime;
-    uint32_t pointerCount;
-    PointerProperties pointerProperties[MAX_POINTERS];
-    PointerCoords pointerCoords[MAX_POINTERS];
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    size_t getPointerCount() const { return pointerProperties.size(); }
 
     MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
                 uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
                 int32_t metaState, int32_t buttonState, MotionClassification classification,
                 int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
-                float yCursorPosition, nsecs_t downTime, uint32_t pointerCount,
-                const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+                float yCursorPosition, nsecs_t downTime,
+                const std::vector<PointerProperties>& pointerProperties,
+                const std::vector<PointerCoords>& pointerCoords);
     std::string getDescription() const override;
 
     ~MotionEntry() override;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 276f75c..7dfbf94 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -365,7 +365,7 @@
     const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
 
     std::vector<PointerCoords> pointerCoords;
-    pointerCoords.resize(motionEntry.pointerCount);
+    pointerCoords.resize(motionEntry.getPointerCount());
 
     // Use the first pointer information to normalize all other pointers. This could be any pointer
     // as long as all other pointers are normalized to the same value and the final DispatchEntry
@@ -375,7 +375,7 @@
     ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
 
     // Iterate through all pointers in the event to normalize against the first.
-    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount; pointerIndex++) {
+    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount(); pointerIndex++) {
         const PointerProperties& pointerProperties = motionEntry.pointerProperties[pointerIndex];
         uint32_t pointerId = uint32_t(pointerProperties.id);
         const ui::Transform& currTransform = inputTarget.pointerTransforms[pointerId];
@@ -399,8 +399,7 @@
                                           motionEntry.edgeFlags, motionEntry.xPrecision,
                                           motionEntry.yPrecision, motionEntry.xCursorPosition,
                                           motionEntry.yCursorPosition, motionEntry.downTime,
-                                          motionEntry.pointerCount, motionEntry.pointerProperties,
-                                          pointerCoords.data());
+                                          motionEntry.pointerProperties, pointerCoords);
 
     if (motionEntry.injectionState) {
         combinedMotionEntry->injectionState = motionEntry.injectionState;
@@ -635,11 +634,10 @@
                                                     const MotionEntry& entry) {
     std::vector<TouchedWindow> out;
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
-    if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-        // Not a hover event - don't need to do anything
-        return out;
+
+    if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+        // ACTION_SCROLL events should not affect the hovering pointer dispatch
+        return {};
     }
 
     // We should consider all hovering pointers here. But for now, just use the first one
@@ -1316,8 +1314,9 @@
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
             std::bitset<MAX_POINTER_ID + 1> pointerIds;
             pointerIds.set(pointerId);
-            addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, pointerIds,
-                                  /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
+            addPointerWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
+                                         pointerIds,
+                                         /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
         }
     }
     return outsideTargets;
@@ -1822,7 +1821,7 @@
     std::vector<InputTarget> inputTargets;
     addWindowTargetLocked(focusedWindow,
                           InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
-                          /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                          getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1906,7 +1905,6 @@
     // Identify targets.
     std::vector<InputTarget> inputTargets;
 
-    bool conflictingPointerActions = false;
     InputEventInjectionResult injectionResult;
     if (isPointerEvent) {
         // Pointer event.  (eg. touchscreen)
@@ -1918,8 +1916,7 @@
         }
 
         inputTargets =
-                findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
-                                               /*byref*/ injectionResult);
+                findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult);
         LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED &&
                             !inputTargets.empty());
     } else {
@@ -1931,7 +1928,7 @@
             addWindowTargetLocked(focusedWindow,
                                   InputTarget::Flags::FOREGROUND |
                                           InputTarget::Flags::DISPATCH_AS_IS,
-                                  /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                                  getDownTime(*entry), inputTargets);
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -1955,11 +1952,6 @@
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
     // Dispatch the motion.
-    if (conflictingPointerActions) {
-        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "conflicting pointer actions");
-        synthesizeCancelationEventsForAllConnectionsLocked(options);
-    }
     dispatchEventLocked(currentTime, entry, inputTargets);
     return true;
 }
@@ -2000,7 +1992,7 @@
               entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
               entry.yPrecision, entry.downTime);
 
-        for (uint32_t i = 0; i < entry.pointerCount; i++) {
+        for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
@@ -2259,7 +2251,7 @@
 }
 
 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
-        nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+        nsecs_t currentTime, const MotionEntry& entry,
         InputEventInjectionResult& outInjectionResult) {
     ATRACE_CALL();
 
@@ -2284,20 +2276,13 @@
     }
 
     bool isSplit = shouldSplitTouch(tempTouchState, entry);
-    bool switchedDevice = false;
-    if (oldState != nullptr) {
-        std::set<int32_t> oldActiveDevices = oldState->getActiveDeviceIds();
-        const bool anotherDeviceIsActive =
-                oldActiveDevices.count(entry.deviceId) == 0 && !oldActiveDevices.empty();
-        switchedDevice |= anotherDeviceIsActive;
-    }
 
     const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
     // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any
     // touchable windows.
-    const bool wasDown = oldState != nullptr && oldState->isDown();
+    const bool wasDown = oldState != nullptr && oldState->isDown(entry.deviceId);
     const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
             (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
     const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
@@ -2305,34 +2290,19 @@
             maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
-    // If pointers are already down, let's finish the current gesture and ignore the new events
-    // from another device. However, if the new event is a down event, let's cancel the current
-    // touch and let the new one take over.
-    if (switchedDevice && wasDown && !isDown) {
-        LOG(INFO) << "Dropping event because a pointer for another device "
-                  << " is already down in display " << displayId << ": " << entry.getDescription();
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (newGesture) {
+        isSplit = false;
     }
 
-    if (newGesture) {
-        // If a new gesture is starting, clear the touch state completely.
-        tempTouchState.reset();
-        isSplit = false;
-    } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
-        ALOGI("Dropping move event because a pointer for a different device is already active "
-              "in display %" PRId32,
-              displayId);
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) {
+        // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated.
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (isHoverAction) {
         // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
         // all of the existing hovering pointers and recompute.
-        tempTouchState.clearHoveringPointers();
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
@@ -2403,8 +2373,7 @@
                 continue;
             }
 
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-                maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+            if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
                 tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
             }
@@ -2427,31 +2396,24 @@
             }
 
             // Update the temporary touch state.
-            std::bitset<MAX_POINTER_ID + 1> pointerIds;
+
             if (!isHoverAction) {
+                std::bitset<MAX_POINTER_ID + 1> pointerIds;
                 pointerIds.set(pointerId);
-            }
-
-            const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
-                    maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-
-            // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would
-            // still add a window to the touch state. We should avoid doing that, but some of the
-            // later checks ("at least one foreground window") rely on this in order to dispatch
-            // the event properly, so that needs to be updated, possibly by looking at InputTargets.
-            tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds,
-                                             isDownOrPointerDown
-                                                     ? std::make_optional(entry.eventTime)
-                                                     : std::nullopt);
-
-            // If this is the pointer going down and the touched window has a wallpaper
-            // then also add the touched wallpaper windows so they are locked in for the duration
-            // of the touch gesture.
-            // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
-            // engine only supports touch events.  We would need to add a mechanism similar
-            // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-            if (isDownOrPointerDown) {
-                if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                        maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+                tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId,
+                                                 pointerIds,
+                                                 isDownOrPointerDown
+                                                         ? std::make_optional(entry.eventTime)
+                                                         : std::nullopt);
+                // If this is the pointer going down and the touched window has a wallpaper
+                // then also add the touched wallpaper windows so they are locked in for the
+                // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
+                // SCROLL because the wallpaper engine only supports touch events.  We would need to
+                // add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to
+                // handle these events.
+                if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                     sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
@@ -2489,8 +2451,10 @@
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
         // If the pointer is not currently down, then ignore the event.
-        if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-            LOG(INFO) << "Dropping event because the pointer is not down or we previously "
+        if (!tempTouchState.isDown(entry.deviceId) &&
+            maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+            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();
             outInjectionResult = InputEventInjectionResult::FAILED;
@@ -2514,7 +2478,7 @@
         addDragEventLocked(entry);
 
         // Check whether touches should slip outside of the current foreground window.
-        if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
+        if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
             tempTouchState.isSlippery()) {
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
@@ -2539,7 +2503,7 @@
 
             if (newTouchedWindowHandle != nullptr &&
                 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
-                ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
+                ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
                       oldTouchedWindowHandle->getName().c_str(),
                       newTouchedWindowHandle->getName().c_str(), displayId);
 
@@ -2550,9 +2514,11 @@
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
-                addWindowTargetLocked(oldTouchedWindowHandle,
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds,
-                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+                addPointerWindowTargetLocked(oldTouchedWindowHandle,
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                             pointerIds,
+                                             touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                             targets);
 
                 // Make a slippery entrance into the new window.
                 if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2665,16 +2631,14 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        if (!touchedWindow.hasTouchingPointers(entry.deviceId) &&
-            !touchedWindow.hasHoveringPointers(entry.deviceId)) {
-            // Windows with hovering pointers are getting persisted inside TouchState.
-            // Do not send this event to those windows.
+        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+                touchedWindow.getTouchingPointers(entry.deviceId);
+        if (touchingPointers.none()) {
             continue;
         }
-
-        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                              touchedWindow.getTouchingPointers(entry.deviceId),
-                              touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+                                     touchingPointers,
+                                     touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
     // During targeted injection, only allow owned targets to receive events
@@ -2707,37 +2671,30 @@
     }
 
     outInjectionResult = InputEventInjectionResult::SUCCEEDED;
-    // Drop the outside or hover touch windows since we will not care about them
-    // in the next iteration.
-    tempTouchState.filterNonAsIsTouchWindows();
 
-    // Update final pieces of touch state if the injector had permission.
-    if (switchedDevice) {
-        if (DEBUG_FOCUS) {
-            ALOGD("Conflicting pointer actions: Switched to a different device.");
+    for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+        // Targets that we entered in a slippery way will now become AS-IS targets
+        if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
+            touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
+            touchedWindow.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS;
         }
-        *outConflictingPointerActions = true;
     }
 
+    // Update final pieces of touch state if the injector had permission.
     if (isHoverAction) {
-        // Started hovering, therefore no longer down.
-        if (oldState && oldState->isDown()) {
-            ALOGD_IF(DEBUG_FOCUS,
-                     "Conflicting pointer actions: Hover received while pointer was down.");
-            *outConflictingPointerActions = true;
+        if (oldState && oldState->isDown(entry.deviceId)) {
+            // Started hovering, but the device is already down: reject the hover event
+            LOG(ERROR) << "Got hover event " << entry.getDescription()
+                       << " but the device is already down " << oldState->dump();
+            outInjectionResult = InputEventInjectionResult::FAILED;
+            return {};
         }
     } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         // Pointer went up.
         tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id);
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
         // All pointers up or canceled.
-        tempTouchState.reset();
-    } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-        // First pointer went down.
-        if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) {
-            ALOGD("Conflicting pointer actions: Down received while already down or hovering.");
-            *outConflictingPointerActions = true;
-        }
+        tempTouchState.removeAllPointersForDevice(entry.deviceId);
     } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
         // One pointer went up.
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
@@ -2793,14 +2750,14 @@
 
     // Find the pointer index by id.
     int32_t pointerIndex = 0;
-    for (; static_cast<uint32_t>(pointerIndex) < entry.pointerCount; pointerIndex++) {
+    for (; static_cast<uint32_t>(pointerIndex) < entry.getPointerCount(); pointerIndex++) {
         const PointerProperties& pointerProperties = entry.pointerProperties[pointerIndex];
         if (pointerProperties.id == mDragState->pointerId) {
             break;
         }
     }
 
-    if (uint32_t(pointerIndex) == entry.pointerCount) {
+    if (uint32_t(pointerIndex) == entry.getPointerCount()) {
         LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId);
     }
 
@@ -2886,7 +2843,6 @@
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
-                                            std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
                                             std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
@@ -2908,8 +2864,54 @@
         it = inputTargets.end() - 1;
     }
 
-    ALOG_ASSERT(it->flags == targetFlags);
-    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
+}
+
+void InputDispatcher::addPointerWindowTargetLocked(
+        const sp<android::gui::WindowInfoHandle>& windowHandle,
+        ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+        std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const
+        REQUIRES(mLock) {
+    if (pointerIds.none()) {
+        for (const auto& target : inputTargets) {
+            LOG(INFO) << "Target: " << target;
+        }
+        LOG(FATAL) << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
+    std::vector<InputTarget>::iterator it =
+            std::find_if(inputTargets.begin(), inputTargets.end(),
+                         [&windowHandle](const InputTarget& inputTarget) {
+                             return inputTarget.inputChannel->getConnectionToken() ==
+                                     windowHandle->getToken();
+                         });
+
+    // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
+    // causes a HOVER_EXIT to be generated. That means that the same entry of ACTION_DOWN would
+    // have DISPATCH_AS_HOVER_EXIT and DISPATCH_AS_IS. And therefore, we have to create separate
+    // input targets for hovering pointers and for touching pointers.
+    // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new
+    // target instead.
+    if (it != inputTargets.end() && it->flags.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
+        // Force the code below to create a new input target
+        it = inputTargets.end();
+    }
+
+    const WindowInfo* windowInfo = windowHandle->getInfo();
+
+    if (it == inputTargets.end()) {
+        std::optional<InputTarget> target =
+                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+        if (!target) {
+            return;
+        }
+        inputTargets.push_back(*target);
+        it = inputTargets.end() - 1;
+    }
+
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
 
     it->addPointers(pointerIds, windowInfo->transform);
 }
@@ -3225,7 +3227,7 @@
                             ftl::enum_string(eventEntry->type).c_str());
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
-        if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
+        if (inputTarget.pointerIds.count() != originalMotionEntry.getPointerCount()) {
             if (!inputTarget.firstDownTimeInTarget.has_value()) {
                 logDispatchStateLocked();
                 LOG(FATAL) << "Splitting motion events requires a down time to be set for the "
@@ -3367,6 +3369,27 @@
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
+            // Check if we need to cancel any of the ongoing gestures. We don't support multiple
+            // devices being active at the same time in the same window, so if a new device is
+            // active, cancel the gesture from the old device.
+
+            std::unique_ptr<EventEntry> cancelEvent =
+                    connection->inputState
+                            .cancelConflictingInputStream(motionEntry,
+                                                          dispatchEntry->resolvedAction);
+            if (cancelEvent != nullptr) {
+                LOG(INFO) << "Canceling pointers for device " << motionEntry.deviceId << " in "
+                          << connection->getInputChannelName() << " with event "
+                          << cancelEvent->getDescription();
+                std::unique_ptr<DispatchEntry> cancelDispatchEntry =
+                        createDispatchEntry(inputTarget, std::move(cancelEvent),
+                                            InputTarget::Flags::DISPATCH_AS_IS);
+
+                // Send these cancel events to the queue before sending the event from the new
+                // device.
+                connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry));
+            }
+
             if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
                                                     dispatchEntry->resolvedFlags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
@@ -3537,14 +3560,14 @@
     const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
 
     PointerCoords scaledCoords[MAX_POINTERS];
-    const PointerCoords* usingCoords = motionEntry.pointerCoords;
+    const PointerCoords* usingCoords = motionEntry.pointerCoords.data();
 
     // Set the X and Y offset and X and Y scale depending on the input source.
     if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
         !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) {
         float globalScaleFactor = dispatchEntry.globalScaleFactor;
         if (globalScaleFactor != 1.0f) {
-            for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+            for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
                 scaledCoords[i] = motionEntry.pointerCoords[i];
                 // Don't apply window scale here since we don't want scale to affect raw
                 // coordinates. The scale will be sent back to the client and applied
@@ -3555,7 +3578,7 @@
         }
     } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) {
         // We don't want the dispatch target to know the coordinates
-        for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+        for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
             scaledCoords[i].clear();
         }
         usingCoords = scaledCoords;
@@ -3575,7 +3598,7 @@
                                 motionEntry.yPrecision, motionEntry.xCursorPosition,
                                 motionEntry.yCursorPosition, dispatchEntry.rawTransform,
                                 motionEntry.downTime, motionEntry.eventTime,
-                                motionEntry.pointerCount, motionEntry.pointerProperties,
+                                motionEntry.getPointerCount(), motionEntry.pointerProperties.data(),
                                 usingCoords);
 }
 
@@ -3958,29 +3981,25 @@
     android_log_event_list(LOGTAG_INPUT_CANCEL)
             << connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
 
-    sp<WindowInfoHandle> windowHandle;
-    if (options.displayId) {
-        windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken(),
-                                             options.displayId.value());
-    } else {
-        windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken());
-    }
-
     const bool wasEmpty = connection->outboundQueue.empty();
+    // The target to use if we don't find a window associated with the channel.
+    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel,
+                                     .flags = InputTarget::Flags::DISPATCH_AS_IS};
+    const auto& token = connection->inputChannel->getConnectionToken();
 
     for (size_t i = 0; i < cancelationEvents.size(); i++) {
         std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]);
         std::vector<InputTarget> targets{};
-        // The target to use if we don't find a window associated with the channel.
-        const InputTarget fallbackTarget{.inputChannel = connection->inputChannel,
-                                         .flags = InputTarget::Flags::DISPATCH_AS_IS};
 
         switch (cancelationEventEntry->type) {
             case EventEntry::Type::KEY: {
                 const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
-                if (windowHandle != nullptr) {
-                    addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_IS,
-                                          /*pointerIds=*/{}, keyEntry.downTime, targets);
+                const std::optional<int32_t> targetDisplay = keyEntry.displayId != ADISPLAY_ID_NONE
+                        ? std::make_optional(keyEntry.displayId)
+                        : std::nullopt;
+                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+                    addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
+                                          keyEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                 }
@@ -3989,14 +4008,18 @@
             }
             case EventEntry::Type::MOTION: {
                 const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
-                if (windowHandle != nullptr) {
+                const std::optional<int32_t> targetDisplay =
+                        motionEntry.displayId != ADISPLAY_ID_NONE
+                        ? std::make_optional(motionEntry.displayId)
+                        : std::nullopt;
+                if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount;
+                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_IS,
-                                          pointerIds, motionEntry.downTime, targets);
+                    addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
+                                                 pointerIds, motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                     const auto it = mDisplayInfos.find(motionEntry.displayId);
@@ -4066,12 +4089,12 @@
                 const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
                 if (windowHandle != nullptr) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount;
+                    for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addWindowTargetLocked(windowHandle, targetFlags, pointerIds,
-                                          motionEntry.downTime, targets);
+                    addPointerWindowTargetLocked(windowHandle, targetFlags, pointerIds,
+                                                 motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
                                                      .flags = targetFlags});
@@ -4127,10 +4150,10 @@
     ALOG_ASSERT(pointerIds.any());
 
     uint32_t splitPointerIndexMap[MAX_POINTERS];
-    PointerProperties splitPointerProperties[MAX_POINTERS];
-    PointerCoords splitPointerCoords[MAX_POINTERS];
+    std::vector<PointerProperties> splitPointerProperties;
+    std::vector<PointerCoords> splitPointerCoords;
 
-    uint32_t originalPointerCount = originalMotionEntry.pointerCount;
+    uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
     uint32_t splitPointerCount = 0;
 
     for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
@@ -4140,9 +4163,8 @@
         uint32_t pointerId = uint32_t(pointerProperties.id);
         if (pointerIds.test(pointerId)) {
             splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
-            splitPointerProperties[splitPointerCount] = pointerProperties;
-            splitPointerCoords[splitPointerCount] =
-                    originalMotionEntry.pointerCoords[originalPointerIndex];
+            splitPointerProperties.push_back(pointerProperties);
+            splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
             splitPointerCount += 1;
         }
     }
@@ -4217,8 +4239,7 @@
                                           originalMotionEntry.yPrecision,
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition, splitDownTime,
-                                          splitPointerCount, splitPointerProperties,
-                                          splitPointerCoords);
+                                          splitPointerProperties, splitPointerCoords);
 
     if (originalMotionEntry.injectionState) {
         splitMotionEntry->injectionState = originalMotionEntry.injectionState;
@@ -4437,9 +4458,8 @@
                                               args.buttonState, args.classification, args.edgeFlags,
                                               args.xPrecision, args.yPrecision,
                                               args.xCursorPosition, args.yCursorPosition,
-                                              args.downTime, args.getPointerCount(),
-                                              args.pointerProperties.data(),
-                                              args.pointerCoords.data());
+                                              args.downTime, args.pointerProperties,
+                                              args.pointerCoords);
 
         if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
@@ -4653,6 +4673,11 @@
 
             mLock.lock();
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
+            const size_t pointerCount = motionEvent.getPointerCount();
+            const std::vector<PointerProperties>
+                    pointerProperties(motionEvent.getPointerProperties(),
+                                      motionEvent.getPointerProperties() + pointerCount);
+
             const PointerCoords* samplePointerCoords = motionEvent.getSamplePointerCoords();
             std::unique_ptr<MotionEntry> injectedEntry =
                     std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
@@ -4667,33 +4692,28 @@
                                                   motionEvent.getYPrecision(),
                                                   motionEvent.getRawXCursorPosition(),
                                                   motionEvent.getRawYCursorPosition(),
-                                                  motionEvent.getDownTime(),
-                                                  motionEvent.getPointerCount(),
-                                                  motionEvent.getPointerProperties(),
-                                                  samplePointerCoords);
+                                                  motionEvent.getDownTime(), pointerProperties,
+                                                  std::vector<PointerCoords>(samplePointerCoords,
+                                                                             samplePointerCoords +
+                                                                                     pointerCount));
             transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
                 samplePointerCoords += motionEvent.getPointerCount();
-                std::unique_ptr<MotionEntry> nextInjectedEntry =
-                        std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
-                                                      resolvedDeviceId, motionEvent.getSource(),
-                                                      displayId, policyFlags,
-                                                      motionEvent.getAction(),
-                                                      motionEvent.getActionButton(), flags,
-                                                      motionEvent.getMetaState(),
-                                                      motionEvent.getButtonState(),
-                                                      motionEvent.getClassification(),
-                                                      motionEvent.getEdgeFlags(),
-                                                      motionEvent.getXPrecision(),
-                                                      motionEvent.getYPrecision(),
-                                                      motionEvent.getRawXCursorPosition(),
-                                                      motionEvent.getRawYCursorPosition(),
-                                                      motionEvent.getDownTime(),
-                                                      motionEvent.getPointerCount(),
-                                                      motionEvent.getPointerProperties(),
-                                                      samplePointerCoords);
+                std::unique_ptr<MotionEntry> nextInjectedEntry = std::make_unique<
+                        MotionEntry>(motionEvent.getId(), *sampleEventTimes, resolvedDeviceId,
+                                     motionEvent.getSource(), displayId, policyFlags,
+                                     motionEvent.getAction(), motionEvent.getActionButton(), flags,
+                                     motionEvent.getMetaState(), motionEvent.getButtonState(),
+                                     motionEvent.getClassification(), motionEvent.getEdgeFlags(),
+                                     motionEvent.getXPrecision(), motionEvent.getYPrecision(),
+                                     motionEvent.getRawXCursorPosition(),
+                                     motionEvent.getRawYCursorPosition(), motionEvent.getDownTime(),
+                                     pointerProperties,
+                                     std::vector<PointerCoords>(samplePointerCoords,
+                                                                samplePointerCoords +
+                                                                        pointerCount));
                 transformMotionEntryForInjectionLocked(*nextInjectedEntry,
                                                        motionEvent.getTransform());
                 injectedEntries.push(std::move(nextInjectedEntry));
@@ -4872,7 +4892,7 @@
         entry.xCursorPosition = cursor.x;
         entry.yCursorPosition = cursor.y;
     }
-    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+    for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         entry.pointerCoords[i] =
                 MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
                                                         entry.pointerCoords[i]);
@@ -4905,29 +4925,26 @@
 }
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
-        const sp<IBinder>& windowHandleToken) const {
+        const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId) const {
     if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
-    for (auto& it : mWindowHandlesByDisplay) {
-        const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
-        for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
-            if (windowHandle->getToken() == windowHandleToken) {
-                return windowHandle;
+    if (!displayId) {
+        // Look through all displays.
+        for (auto& it : mWindowHandlesByDisplay) {
+            const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+            for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+                if (windowHandle->getToken() == windowHandleToken) {
+                    return windowHandle;
+                }
             }
         }
-    }
-    return nullptr;
-}
-
-sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
-                                                            int displayId) const {
-    if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
-    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
+    // Only look through the requested display.
+    for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(*displayId)) {
         if (windowHandle->getToken() == windowHandleToken) {
             return windowHandle;
         }
@@ -5146,10 +5163,8 @@
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
             if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Touched window was removed: %s in display %" PRId32,
-                          touchedWindow.windowHandle->getName().c_str(), displayId);
-                }
+                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                          << " in display %" << displayId;
                 std::shared_ptr<InputChannel> touchedInputChannel =
                         getInputChannelLocked(touchedWindow.windowHandle->getToken());
                 if (touchedInputChannel != nullptr) {
@@ -5980,9 +5995,8 @@
         return BAD_VALUE;
     }
     std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
-    if (deviceIds.size() != 1) {
-        LOG(WARNING) << "Can't pilfer. Currently touching devices: " << dumpSet(deviceIds)
-                     << " in window: " << windowPtr->dump();
+    if (deviceIds.empty()) {
+        LOG(WARNING) << "Can't pilfer: no touching devices in window: " << windowPtr->dump();
         return BAD_VALUE;
     }
 
@@ -6825,10 +6839,11 @@
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        addWindowTargetLocked(oldWallpaper,
-                              oldTouchedWindow.targetFlags |
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                              pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
+        addPointerWindowTargetLocked(oldWallpaper,
+                                     oldTouchedWindow.targetFlags |
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                     pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId),
+                                     targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
     }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index ee5a797..a1127a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -354,14 +354,11 @@
     // Get a reference to window handles by display, return an empty vector if not found.
     const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
             int32_t displayId) const REQUIRES(mLock);
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<IBinder>& windowHandleToken) const REQUIRES(mLock);
     ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
 
-    // Same function as above, but faster. Since displayId is provided, this avoids the need
-    // to loop through all displays.
-    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
-                                                             int displayId) const REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
+            const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId = {}) const
+            REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
     std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const
@@ -527,7 +524,7 @@
             nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
-            nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+            nsecs_t currentTime, const MotionEntry& entry,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
@@ -538,9 +535,13 @@
             std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                ftl::Flags<InputTarget::Flags> targetFlags,
-                               std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                std::optional<nsecs_t> firstDownTimeInTarget,
                                std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
+    void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                      ftl::Flags<InputTarget::Flags> targetFlags,
+                                      std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                      std::optional<nsecs_t> firstDownTimeInTarget,
+                                      std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index b21427d..09b5186 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,6 +24,19 @@
 
 namespace android::inputdispatcher {
 
+namespace {
+bool isHoverAction(int32_t action) {
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            return true;
+        }
+    }
+    return false;
+}
+} // namespace
+
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
@@ -89,6 +102,28 @@
  *  false if the incoming event should be dropped.
  */
 bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
+    // Don't track non-pointer events
+    if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event; we don't track its state.
+        return true;
+    }
+
+    if (!mMotionMementos.empty()) {
+        const MotionMemento& lastMemento = mMotionMementos.back();
+        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+            !isStylusEvent(entry.source, entry.pointerProperties)) {
+            // We already have a stylus stream, and the new event is not from stylus.
+            if (!lastMemento.hovering) {
+                // If stylus is currently down, reject the new event unconditionally.
+                return false;
+            }
+        }
+        if (!lastMemento.hovering && isHoverAction(action)) {
+            // Reject hovers if already down
+            return false;
+        }
+    }
+
     int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_UP:
@@ -235,8 +270,10 @@
 }
 
 void InputState::MotionMemento::setPointers(const MotionEntry& entry) {
-    pointerCount = 0;
-    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+    pointerProperties.clear();
+    pointerCoords.clear();
+
+    for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
             // In POINTER_UP events, the pointer is leaving. Since the action is not stored,
             // this departing pointer should not be recorded.
@@ -245,23 +282,155 @@
                 continue;
             }
         }
-        pointerProperties[pointerCount] = entry.pointerProperties[i];
-        pointerCoords[pointerCount] = entry.pointerCoords[i];
-        pointerCount++;
+        pointerProperties.push_back(entry.pointerProperties[i]);
+        pointerCoords.push_back(entry.pointerCoords[i]);
     }
 }
 
 void InputState::MotionMemento::mergePointerStateTo(MotionMemento& other) const {
-    for (uint32_t i = 0; i < pointerCount; i++) {
+    for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (other.firstNewPointerIdx < 0) {
-            other.firstNewPointerIdx = other.pointerCount;
+            other.firstNewPointerIdx = other.getPointerCount();
         }
-        other.pointerProperties[other.pointerCount] = pointerProperties[i];
-        other.pointerCoords[other.pointerCount] = pointerCoords[i];
-        other.pointerCount++;
+        other.pointerProperties.push_back(pointerProperties[i]);
+        other.pointerCoords.push_back(pointerCoords[i]);
     }
 }
 
+size_t InputState::MotionMemento::getPointerCount() const {
+    return pointerProperties.size();
+}
+
+bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry,
+                                            int32_t resolvedAction) const {
+    if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event that should not affect the previous stream.
+        return false;
+    }
+
+    // New MotionEntry pointer event is coming in.
+
+    // If this is a new gesture, and it's from a different device, then, in general, we will cancel
+    // the current gesture.
+    // However, because stylus should be preferred over touch, we need to treat some cases in a
+    // special way.
+    if (mMotionMementos.empty()) {
+        // There is no ongoing pointer gesture, so there is nothing to cancel
+        return false;
+    }
+
+    const MotionMemento& lastMemento = mMotionMementos.back();
+    const int32_t actionMasked = MotionEvent::getActionMasked(resolvedAction);
+
+    // For compatibility, only one input device can be active at a time in the same window.
+    if (lastMemento.deviceId == motionEntry.deviceId) {
+        // In general, the same device should produce self-consistent streams so nothing needs to
+        // be canceled. But there is one exception:
+        // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for
+        // that, cancel the previous hovering stream
+        if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) {
+            return true;
+        }
+
+        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
+        // If this hover event was generated as a result of the pointer leaving the window,
+        // the HOVER_EXIT event should have the same coordinates as the previous
+        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
+        // coordinates as the previous event by cancelling the stream here. With this approach, the
+        // HOVER_EXIT event is generated from the previous event.
+        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
+            return true;
+        }
+
+        // If the stream changes its source, just cancel the current gesture to be safe. It's
+        // possible that the app isn't handling source changes properly
+        if (motionEntry.source != lastMemento.source) {
+            LOG(INFO) << "Canceling stream: last source was "
+                      << inputEventSourceToString(lastMemento.source) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        // If the injection is happening into two different displays, the same injected device id
+        // could be going into both. And at this time, if mirroring is active, the same connection
+        // 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) {
+            LOG(INFO) << "Canceling stream: last displayId was "
+                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        return false;
+    }
+
+    // We want stylus down to block touch and other source types, but stylus hover should not
+    // have such an effect.
+    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
+        // New event is a hover. Keep the current non-hovering gesture instead
+        return false;
+    }
+
+    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
+        // We have non-hovering stylus already active.
+        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+            // If this new event is a stylus from a different device going down, then cancel the old
+            // stylus and allow the new stylus to take over
+            return true;
+        }
+
+        // Keep the current stylus gesture.
+        return false;
+    }
+
+    // Cancel the current gesture if this is a start of a new gesture from a new device.
+    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+        actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+        return true;
+    }
+    // By default, don't cancel any events.
+    return false;
+}
+
+std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                                     int32_t resolvedAction) {
+    if (!shouldCancelPreviousStream(motionEntry, resolvedAction)) {
+        return {};
+    }
+
+    const MotionMemento& memento = mMotionMementos.back();
+
+    // Cancel the last device stream
+    std::unique_ptr<MotionEntry> cancelEntry =
+            createCancelEntryForMemento(memento, motionEntry.eventTime);
+
+    if (!trackMotion(*cancelEntry, cancelEntry->action, cancelEntry->flags)) {
+        LOG(FATAL) << "Generated inconsistent cancel event!";
+    }
+    return cancelEntry;
+}
+
+std::unique_ptr<MotionEntry> InputState::createCancelEntryForMemento(const MotionMemento& memento,
+                                                                     nsecs_t eventTime) const {
+    const int32_t action =
+            memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+    int32_t flags = memento.flags;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    return std::make_unique<MotionEntry>(mIdGenerator.nextId(), eventTime, memento.deviceId,
+                                         memento.source, memento.displayId, memento.policyFlags,
+                                         action, /*actionButton=*/0, flags, AMETA_NONE,
+                                         /*buttonState=*/0, MotionClassification::NONE,
+                                         AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+                                         memento.yPrecision, memento.xCursorPosition,
+                                         memento.yCursorPosition, memento.downTime,
+                                         memento.pointerProperties, memento.pointerCoords);
+}
+
 std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents(
         nsecs_t currentTime, const CancelationOptions& options) {
     std::vector<std::unique_ptr<EventEntry>> events;
@@ -280,25 +449,7 @@
     for (const MotionMemento& memento : mMotionMementos) {
         if (shouldCancelMotion(memento, options)) {
             if (options.pointerIds == std::nullopt) {
-                const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
-                                                        : AMOTION_EVENT_ACTION_CANCEL;
-                int32_t flags = memento.flags;
-                if (action == AMOTION_EVENT_ACTION_CANCEL) {
-                    flags |= AMOTION_EVENT_FLAG_CANCELED;
-                }
-                events.push_back(
-                        std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
-                                                      memento.deviceId, memento.source,
-                                                      memento.displayId, memento.policyFlags,
-                                                      action, /*actionButton=*/0, flags, AMETA_NONE,
-                                                      /*buttonState=*/0, MotionClassification::NONE,
-                                                      AMOTION_EVENT_EDGE_FLAG_NONE,
-                                                      memento.xPrecision, memento.yPrecision,
-                                                      memento.xCursorPosition,
-                                                      memento.yCursorPosition, memento.downTime,
-                                                      memento.pointerCount,
-                                                      memento.pointerProperties,
-                                                      memento.pointerCoords));
+                events.push_back(createCancelEntryForMemento(memento, currentTime));
             } else {
                 std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents =
                         synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(),
@@ -323,23 +474,22 @@
             continue;
         }
 
-        uint32_t pointerCount = 0;
-        PointerProperties pointerProperties[MAX_POINTERS];
-        PointerCoords pointerCoords[MAX_POINTERS];
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
 
         // We will deliver all pointers the target already knows about
         for (uint32_t i = 0; i < static_cast<uint32_t>(memento.firstNewPointerIdx); i++) {
-            pointerProperties[i] = memento.pointerProperties[i];
-            pointerCoords[i] = memento.pointerCoords[i];
-            pointerCount++;
+            pointerProperties.push_back(memento.pointerProperties[i]);
+            pointerCoords.push_back(memento.pointerCoords[i]);
         }
 
         // We will send explicit events for all pointers the target doesn't know about
         for (uint32_t i = static_cast<uint32_t>(memento.firstNewPointerIdx);
-                i < memento.pointerCount; i++) {
-            pointerProperties[i] = memento.pointerProperties[i];
-            pointerCoords[i] = memento.pointerCoords[i];
-            pointerCount++;
+             i < memento.getPointerCount(); i++) {
+            pointerProperties.push_back(memento.pointerProperties[i]);
+            pointerCoords.push_back(memento.pointerCoords[i]);
+
+            const size_t pointerCount = pointerProperties.size();
 
             // Down only if the first pointer, pointer down otherwise
             const int32_t action = (pointerCount <= 1)
@@ -356,7 +506,7 @@
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
-                                                  pointerCount, pointerProperties, pointerCoords));
+                                                  pointerProperties, pointerCoords));
         }
 
         memento.firstNewPointerIdx = INVALID_POINTER_INDEX;
@@ -372,7 +522,7 @@
     std::vector<uint32_t> canceledPointerIndices;
     std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
     std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
-    for (uint32_t pointerIdx = 0; pointerIdx < memento.pointerCount; pointerIdx++) {
+    for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
         uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
         pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
         pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
@@ -381,7 +531,7 @@
         }
     }
 
-    if (canceledPointerIndices.size() == memento.pointerCount) {
+    if (canceledPointerIndices.size() == memento.getPointerCount()) {
         const int32_t action =
                 memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
         int32_t flags = memento.flags;
@@ -397,8 +547,7 @@
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
                                               memento.yCursorPosition, memento.downTime,
-                                              memento.pointerCount, memento.pointerProperties,
-                                              memento.pointerCoords));
+                                              memento.pointerProperties, memento.pointerCoords));
     } else {
         // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
         // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
@@ -409,7 +558,7 @@
         std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
                   std::greater<uint32_t>());
 
-        uint32_t pointerCount = memento.pointerCount;
+        uint32_t pointerCount = memento.getPointerCount();
         for (const uint32_t pointerIdx : canceledPointerIndices) {
             const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
                                                      : AMOTION_EVENT_ACTION_POINTER_UP |
@@ -425,8 +574,7 @@
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
-                                                  pointerCount, pointerProperties.data(),
-                                                  pointerCoords.data()));
+                                                  pointerProperties, pointerCoords));
 
             // Cleanup pointer information
             pointerProperties.erase(pointerProperties.begin() + pointerIdx);
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 32df034..686c432 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -48,6 +48,10 @@
     // and should be skipped.
     bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags);
 
+    // Create cancel events for the previous stream if the current motionEntry requires it.
+    std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                             int32_t resolvedAction);
+
     // Synthesizes cancelation events for the current state and resets the tracked state.
     std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents(
             nsecs_t currentTime, const CancelationOptions& options);
@@ -97,9 +101,8 @@
         float xCursorPosition;
         float yCursorPosition;
         nsecs_t downTime;
-        uint32_t pointerCount;
-        PointerProperties pointerProperties[MAX_POINTERS];
-        PointerCoords pointerCoords[MAX_POINTERS];
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
         // Track for which pointers the target doesn't know about.
         int32_t firstNewPointerIdx = INVALID_POINTER_INDEX;
         bool hovering;
@@ -107,6 +110,7 @@
 
         void setPointers(const MotionEntry& entry);
         void mergePointerStateTo(MotionMemento& other) const;
+        size_t getPointerCount() const;
     };
 
     const IdGenerator& mIdGenerator; // InputDispatcher owns it so we won't have dangling reference.
@@ -123,6 +127,9 @@
 
     static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
     static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
+    bool shouldCancelPreviousStream(const MotionEntry& motionEntry, int32_t resolvedAction) const;
+    std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento,
+                                                             nsecs_t eventTime) const;
 
     // Synthesizes pointer cancel events for a particular set of pointers.
     std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 9dcf615..2ead171 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <gui/WindowInfo.h>
 
@@ -31,15 +32,6 @@
     *this = TouchState();
 }
 
-std::set<int32_t> TouchState::getActiveDeviceIds() const {
-    std::set<int32_t> out;
-    for (const TouchedWindow& w : windows) {
-        std::set<int32_t> deviceIds = w.getActiveDeviceIds();
-        out.insert(deviceIds.begin(), deviceIds.end());
-    }
-    return out;
-}
-
 bool TouchState::hasTouchingPointers(DeviceId deviceId) const {
     return std::any_of(windows.begin(), windows.end(), [&](const TouchedWindow& window) {
         return window.hasTouchingPointers(deviceId);
@@ -65,9 +57,9 @@
     }
 }
 
-void TouchState::clearHoveringPointers() {
+void TouchState::clearHoveringPointers(DeviceId deviceId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.clearHoveringPointers();
+        touchedWindow.removeAllHoveringPointersForDevice(deviceId);
     }
     clearWindowsWithoutPointers();
 }
@@ -82,9 +74,16 @@
                                    ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                                    std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
+    if (touchingPointerIds.none()) {
+        LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
-        // may have a different transform
+        // may have a different transform. They will be combined later when we create InputTargets.
+        // At that point, per-pointer window transform will be considered.
+        // An alternative design choice here would have been to compare here by token, but to
+        // store per-pointer transform.
         if (touchedWindow.windowHandle == windowHandle) {
             touchedWindow.targetFlags |= targetFlags;
             if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
@@ -237,14 +236,16 @@
     return *it;
 }
 
-bool TouchState::isDown() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasTouchingPointers(); });
+bool TouchState::isDown(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasTouchingPointers(deviceId);
+    });
 }
 
-bool TouchState::hasHoveringPointers() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasHoveringPointers(); });
+bool TouchState::hasHoveringPointers(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasHoveringPointers(deviceId);
+    });
 }
 
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index f016936..e79c73b 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -39,8 +39,6 @@
     void reset();
     void clearWindowsWithoutPointers();
 
-    std::set<DeviceId> getActiveDeviceIds() const;
-
     bool hasTouchingPointers(DeviceId deviceId) const;
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
@@ -52,7 +50,7 @@
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                     DeviceId deviceId, int32_t hoveringPointerId);
     void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
-    void clearHoveringPointers();
+    void clearHoveringPointers(DeviceId deviceId);
 
     void removeAllPointersForDevice(DeviceId deviceId);
     void removeWindowByToken(const sp<IBinder>& token);
@@ -72,8 +70,8 @@
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
-    bool isDown() const;
-    bool hasHoveringPointers() const;
+    bool isDown(DeviceId deviceId) const;
+    bool hasHoveringPointers(DeviceId deviceId) const;
 
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             DeviceId deviceId, int32_t pointerId) const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index ff4b425..5367751 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -45,12 +45,16 @@
     return state.hoveringPointerIds.any();
 }
 
-void TouchedWindow::clearHoveringPointers() {
-    for (auto& [_, state] : mDeviceStates) {
-        state.hoveringPointerIds.reset();
+void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
+    auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return;
     }
-
-    std::erase_if(mDeviceStates, [](const auto& pair) { return !pair.second.hasPointers(); });
+    DeviceState& state = stateIt->second;
+    state.hoveringPointerIds.reset();
+    if (!state.hasPointers()) {
+        mDeviceStates.erase(stateIt);
+    }
 }
 
 bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const {
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 3f760c0..6d2283e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -71,7 +71,7 @@
 
     void removeAllTouchingPointersForDevice(DeviceId deviceId);
     void removeAllHoveringPointersForDevice(DeviceId deviceId);
-    void clearHoveringPointers();
+    void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
 private:
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 90bd7c9..3c26d1d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3782,34 +3782,26 @@
     return out;
 }
 
-static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) {
-    if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
-        return false;
-    }
-    const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-    return isStylusToolType(properties[actionIndex].toolType);
-}
-
 NotifyMotionArgs TouchInputMapper::dispatchMotion(
         nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
         int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
         int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
         const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
         float yPrecision, nsecs_t downTime, MotionClassification classification) {
-    PointerCoords pointerCoords[MAX_POINTERS];
-    PointerProperties pointerProperties[MAX_POINTERS];
+    std::vector<PointerCoords> pointerCoords;
+    std::vector<PointerProperties> pointerProperties;
     uint32_t pointerCount = 0;
     while (!idBits.isEmpty()) {
         uint32_t id = idBits.clearFirstMarkedBit();
         uint32_t index = idToIndex[id];
-        pointerProperties[pointerCount] = properties[index];
-        pointerCoords[pointerCount] = coords[index];
+        pointerProperties.push_back(properties[index]);
+        pointerCoords.push_back(coords[index]);
 
         if (changedId >= 0 && id == uint32_t(changedId)) {
             action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
         }
 
-        pointerCount += 1;
+        pointerCount++;
     }
 
     ALOG_ASSERT(pointerCount != 0);
@@ -3837,7 +3829,7 @@
 
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
-            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties) &&
+            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) &&
             mPointerController && displayId != ADISPLAY_ID_NONE &&
             displayId == mPointerController->getDisplayId();
     if (showDirectStylusPointer) {
@@ -3869,9 +3861,9 @@
                   [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
     return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                             policyFlags, action, actionButton, flags, metaState, buttonState,
-                            classification, edgeFlags, pointerCount, pointerProperties,
-                            pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
-                            downTime, std::move(frames));
+                            classification, edgeFlags, pointerCount, pointerProperties.data(),
+                            pointerCoords.data(), xPrecision, yPrecision, xCursorPosition,
+                            yCursorPosition, downTime, std::move(frames));
 }
 
 std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 2f63b2a..3c87f71 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2409,9 +2409,10 @@
 using InputDispatcherMultiDeviceTest = InputDispatcherTest;
 
 /**
- * One window. Stylus down on the window. Next, touch from another device goes down.
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is dropped, because stylus should be preferred over touch.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusDownAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2434,34 +2435,31 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
                                       .build());
-    // Touch cancels stylus
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId),
-                                     WithCoords(100, 110)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
-                                     WithCoords(140, 145)));
 
     // Touch move
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(141, 146)));
+    // Touch is ignored because stylus is already down
 
-    // Subsequent stylus movements are dropped
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
     window->assertNoEvents();
 }
 
 /**
  * One window and one spy window. Stylus down on the window. Next, touch from another device goes
- * down.
+ * down. Ensure that touch is dropped, because stylus should be preferred over touch.
  * Similar test as above, but with added SPY window.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2497,30 +2495,28 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    // Subsequent stylus movements are dropped
+
+    // Touch is ignored because stylus is already down
+
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                        WithCoords(101, 111)));
 
     window->assertNoEvents();
     spyWindow->assertNoEvents();
 }
 
 /**
- * One window. Stylus hover on the window. Next, touch from another device goes down.
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover should be ignored.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2548,19 +2544,174 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover is canceled because touch is down
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
+                                     WithCoords(140, 145)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(141, 146)));
+
     // Subsequent stylus movements are ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+
+    // but subsequent touches continue to be delivered
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    // Stylus hover movement is dropped
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    // Subsequent touch movements are delivered correctly
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
+ * become active.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t stylusDeviceId1 = 3;
+    constexpr int32_t stylusDeviceId2 = 5;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+    // Second stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+                                      .build());
+
+    // First stylus is canceled, second one takes over.
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    // Subsequent stylus movements are delivered correctly
     window->assertNoEvents();
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that is canceled, because stylus down should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
@@ -2617,8 +2768,8 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
+    leftWindow->assertNoEvents();
+
     rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Second touch pointer down on left window
@@ -2627,6 +2778,11 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
                                       .build());
+    // Since this is now a new splittable pointer going down on the left window, and it's coming
+    // from a different device, the current gesture in the left window (pointer down) should first
+    // be canceled.
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
     // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
@@ -2672,8 +2828,6 @@
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
 
@@ -2683,18 +2837,14 @@
                                       .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
                                       .build());
     leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
-    rightWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(mouseDeviceId)));
 
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(310).y(110))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
     rightWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     leftWindow->assertNoEvents();
     rightWindow->assertNoEvents();
@@ -2745,29 +2895,30 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+    leftWindow->assertNoEvents();
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue, but are ignored because the touch went down more recently.
+    // Spy window does not receive touch events, because stylus events take precedence, and it
+    // already has an active stylus gesture.
+
+    // Stylus movements continue. They should be delivered to the left window and to the spy window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
 
+    // Further MOVE events keep going to the right window only
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
                                       .build());
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
 
     spyWindow->assertNoEvents();
     leftWindow->assertNoEvents();
@@ -2779,8 +2930,10 @@
  * both.
  * Check hover in left window and touch down in the right window.
  * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverAndTouchWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindow =
@@ -2813,13 +2966,13 @@
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
 
-    // Touch down on the right window.
+    // Touch down on the right window. Spy doesn't receive this touch because it already has
+    // stylus hovering there.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+    leftWindow->assertNoEvents();
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
     spyWindow->consumeMotionEvent(
@@ -2827,11 +2980,13 @@
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue, but are ignored because the touch is down.
+    // Stylus movements continue. They should be delivered to the left window only.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     // Touch movements continue. They should be delivered to the right window and to the spy
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
@@ -3054,7 +3209,7 @@
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchTap) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3080,20 +3235,20 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
+    // The touch device should cause hover to stop!
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Continue hovering with stylus. Injection will fail because touch is already down.
-    ASSERT_EQ(InputEventInjectionResult::FAILED,
+    // Continue hovering with stylus.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                         .build()));
-    // No event should be sent. This event should be ignored because a pointer from another device
-    // is already down.
+    // Hovers are now ignored
 
     // Lift up the finger
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3105,7 +3260,6 @@
                                         .build()));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
 
-    // Now that the touch is gone, stylus hovering should start working again
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
@@ -4682,6 +4836,38 @@
     firstWindow->assertNoEvents();
 }
 
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+       SynthesizeHoverEnterExitWithCorrectCoordinatesWhenMirrored) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+    ui::Transform secondDisplayTransform;
+    secondDisplayTransform.set(matrix);
+    addDisplayInfo(SECOND_DISPLAY_ID, secondDisplayTransform);
+
+    sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+    secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+    addWindow(secondWindowClone);
+
+    // Send hover move to the second window, and ensure it shows up as hover enter.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880)));
+
+    // Touch down at the same location and ensure a hover exit is synthesized for the correct
+    // display.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880)));
+    secondWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80), WithRawCoords(300, 880)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverCancelationWithCorrectCoordinates) {
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
 
@@ -4699,6 +4885,37 @@
     firstWindow->assertNoEvents();
 }
 
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+       SynthesizeHoverCancelationWithCorrectCoordinatesWhenMirrored) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+    ui::Transform secondDisplayTransform;
+    secondDisplayTransform.set(matrix);
+    addDisplayInfo(SECOND_DISPLAY_ID, secondDisplayTransform);
+
+    sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+    secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+    addWindow(secondWindowClone);
+
+    // Send hover enter to second window
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                           WithCoords(100, 80), WithRawCoords(300, 880),
+                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    mDispatcher->cancelCurrentTouch();
+
+    // Ensure the cancelation happens with the correct displayId and the correct coordinates.
+    secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+                                           WithRawCoords(300, 880),
+                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    secondWindow->assertNoEvents();
+    firstWindow->assertNoEvents();
+}
+
 /** Ensure consistent behavior of InputDispatcher in all orientations. */
 class InputDispatcherDisplayOrientationFixture
       : public InputDispatcherDisplayProjectionTest,
@@ -7759,10 +7976,23 @@
 // should be ANR'd first.
 TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     mFocusedWindow->consumeMotionDown();
+    mFocusedWindow->consumeMotionUp();
     mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
@@ -7770,17 +8000,20 @@
     mFakePolicy->assertNotifyAnrWasNotCalled();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(unfocusedSequenceNum);
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
-    // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
-    // sequence to make it consistent
-    mFocusedWindow->consumeMotionCancel();
+
     mUnfocusedWindow->finishEvent(*unfocusedSequenceNum);
     mFocusedWindow->consumeMotionDown();
     // This cancel is generated because the connection was unresponsive
@@ -10293,13 +10526,12 @@
                     .build());
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spy->assertNoEvents();
 
     // Act: pilfer from spy. Spy is currently receiving touch events.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
 
@@ -10313,7 +10545,7 @@
                     .deviceId(touchDeviceId)
                     .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
                     .build());
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
 
     spy->assertNoEvents();
     leftWindow->assertNoEvents();
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index 3155b4c..dd83fde 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -37,6 +37,8 @@
 #include <cinttypes>
 #include <cstddef>
 #include <thread>
+#include <mutex>
+#include <condition_variable>
 
 using namespace android::hardware::sensors;
 using android::util::ProtoOutputStream;
@@ -65,7 +67,7 @@
 
 } // anonymous namespace
 
-SensorDevice::SensorDevice() {
+SensorDevice::SensorDevice() : mInHalBypassMode(false) {
     if (!connectHalService()) {
         return;
     }
@@ -352,13 +354,17 @@
     if (mHalWrapper == nullptr) return NO_INIT;
 
     ssize_t eventsRead = 0;
-    if (mHalWrapper->supportsMessageQueues()) {
-        eventsRead = mHalWrapper->pollFmq(buffer, count);
-    } else if (mHalWrapper->supportsPolling()) {
-        eventsRead = mHalWrapper->poll(buffer, count);
+    if (mInHalBypassMode) [[unlikely]] {
+        eventsRead = getHalBypassInjectedEvents(buffer, count);
     } else {
-        ALOGE("Must support polling or FMQ");
-        eventsRead = -1;
+        if (mHalWrapper->supportsMessageQueues()) {
+            eventsRead = mHalWrapper->pollFmq(buffer, count);
+        } else if (mHalWrapper->supportsPolling()) {
+            eventsRead = mHalWrapper->poll(buffer, count);
+        } else {
+            ALOGE("Must support polling or FMQ");
+            eventsRead = -1;
+        }
     }
 
     if (eventsRead > 0) {
@@ -762,11 +768,38 @@
              injected_sensor_event->data[2], injected_sensor_event->data[3],
              injected_sensor_event->data[4], injected_sensor_event->data[5]);
 
+    if (mInHalBypassMode) {
+        std::lock_guard _l(mHalBypassLock);
+        mHalBypassInjectedEventQueue.push(*injected_sensor_event);
+        mHalBypassCV.notify_one();
+        return OK;
+    }
     return mHalWrapper->injectSensorData(injected_sensor_event);
 }
 
 status_t SensorDevice::setMode(uint32_t mode) {
     if (mHalWrapper == nullptr) return NO_INIT;
+    if (mode == SensorService::Mode::HAL_BYPASS_REPLAY_DATA_INJECTION) {
+        if (!mInHalBypassMode) {
+            std::lock_guard _l(mHalBypassLock);
+            while (!mHalBypassInjectedEventQueue.empty()) {
+                // flush any stale events from the injected event queue
+                mHalBypassInjectedEventQueue.pop();
+            }
+            mInHalBypassMode = true;
+        }
+        return OK;
+    } else {
+        if (mInHalBypassMode) {
+            // We are transitioning out of HAL Bypass mode. We need to notify the reader thread
+            // (specifically getHalBypassInjectedEvents()) of this change in state so that it is not
+            // stuck waiting on more injected events to come and therefore preventing events coming
+            // from the HAL from being read.
+            std::lock_guard _l(mHalBypassLock);
+            mInHalBypassMode = false;
+            mHalBypassCV.notify_one();
+        }
+    }
     return mHalWrapper->setOperationMode(static_cast<SensorService::Mode>(mode));
 }
 
@@ -872,5 +905,24 @@
     return 0;
 }
 
+ssize_t SensorDevice::getHalBypassInjectedEvents(sensors_event_t* buffer,
+                                                 size_t maxNumEventsToRead) {
+    std::unique_lock _l(mHalBypassLock);
+    if (mHalBypassInjectedEventQueue.empty()) {
+        // if the injected event queue is empty, block and wait till there are events to process
+        // or if we are no longer in HAL Bypass mode so that this method is not called in a tight
+        // loop. Otherwise, continue copying the injected events into the supplied buffer.
+        mHalBypassCV.wait(_l, [this] {
+            return (!mHalBypassInjectedEventQueue.empty() || !mInHalBypassMode);
+        });
+    }
+    size_t eventsToRead = std::min(mHalBypassInjectedEventQueue.size(), maxNumEventsToRead);
+    for (size_t i = 0; i < eventsToRead; i++) {
+        buffer[i] = mHalBypassInjectedEventQueue.front();
+        mHalBypassInjectedEventQueue.pop();
+    }
+    return eventsToRead;
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index 747a6b0..f127c0f 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -35,6 +35,9 @@
 #include <utils/Timers.h>
 
 #include <algorithm> //std::max std::min
+#include <condition_variable>
+#include <mutex>
+#include <queue>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -225,6 +228,12 @@
     float getResolutionForSensor(int sensorHandle);
 
     bool mIsDirectReportSupported;
+
+    std::mutex mHalBypassLock;
+    std::condition_variable mHalBypassCV;
+    std::queue<sensors_event_t> mHalBypassInjectedEventQueue;
+    ssize_t getHalBypassInjectedEvents(sensors_event_t* buffer, size_t count);
+    bool mInHalBypassMode;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 9c51fd9..44d0d70 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -665,6 +665,10 @@
                    result.appendFormat(" REPLAY_DATA_INJECTION : %s\n",
                             mAllowListedPackage.c_str());
                    break;
+               case HAL_BYPASS_REPLAY_DATA_INJECTION:
+                   result.appendFormat(" HAL_BYPASS_REPLAY_DATA_INJECTION : %s\n",
+                            mAllowListedPackage.c_str());
+                   break;
                default:
                    result.appendFormat(" UNKNOWN\n");
                    break;
@@ -1529,10 +1533,9 @@
 
 sp<ISensorEventConnection> SensorService::createSensorEventConnection(const String8& packageName,
         int requestedMode, const String16& opPackageName, const String16& attributionTag) {
-    // Only 3 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION and
-    // REPLAY_DATA_INJECTION.
-    if (requestedMode != NORMAL && requestedMode != DATA_INJECTION &&
-            requestedMode != REPLAY_DATA_INJECTION) {
+    // Only 4 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION,
+    // REPLAY_DATA_INJECTION and HAL_BYPASS_REPLAY_DATA_INJECTION
+    if (requestedMode != NORMAL && !isInjectionMode(requestedMode)) {
         return nullptr;
     }
     resetTargetSdkVersionCache(opPackageName);
@@ -1553,9 +1556,9 @@
     String16 connOpPackageName =
             (opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
     sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
-            requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION,
-            connOpPackageName, attributionTag));
-    if (requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION) {
+                                                               isInjectionMode(requestedMode),
+                                                               connOpPackageName, attributionTag));
+    if (isInjectionMode(requestedMode)) {
         mConnectionHolder.addEventConnectionIfNotPresent(result);
         // Add the associated file descriptor to the Looper for polling whenever there is data to
         // be injected.
@@ -1566,7 +1569,22 @@
 
 int SensorService::isDataInjectionEnabled() {
     Mutex::Autolock _l(mLock);
-    return (mCurrentOperatingMode == DATA_INJECTION);
+    return mCurrentOperatingMode == DATA_INJECTION;
+}
+
+int SensorService::isReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    return mCurrentOperatingMode == REPLAY_DATA_INJECTION;
+}
+
+int SensorService::isHalBypassReplayDataInjectionEnabled() {
+    Mutex::Autolock _l(mLock);
+    return mCurrentOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION;
+}
+
+bool SensorService::isInjectionMode(int mode) {
+    return (mode == DATA_INJECTION || mode == REPLAY_DATA_INJECTION ||
+            mode == HAL_BYPASS_REPLAY_DATA_INJECTION);
 }
 
 sp<ISensorEventConnection> SensorService::createSensorDirectConnection(
@@ -2332,6 +2350,10 @@
       *targetModeOut = REPLAY_DATA_INJECTION;
       return true;
     }
+    if (inputString == std::string("hal_bypass_replay_data_injection")) {
+      *targetModeOut = HAL_BYPASS_REPLAY_DATA_INJECTION;
+      return true;
+    }
     return false;
 }
 
@@ -2357,7 +2379,8 @@
             dev.disableAllSensors();
         }
         if (mCurrentOperatingMode == DATA_INJECTION ||
-                mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+                mCurrentOperatingMode == REPLAY_DATA_INJECTION ||
+                mCurrentOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION) {
           resetToNormalModeLocked();
         }
         mAllowListedPackage.clear();
@@ -2373,6 +2396,8 @@
         disableAllSensorsLocked(&connLock);
         mAllowListedPackage = String8(args[1]);
         return status_t(NO_ERROR);
+      case HAL_BYPASS_REPLAY_DATA_INJECTION:
+        FALLTHROUGH_INTENDED;
       case REPLAY_DATA_INJECTION:
         if (SensorServiceUtil::isUserBuild()) {
             return INVALID_OPERATION;
@@ -2381,9 +2406,16 @@
       case DATA_INJECTION:
         if (mCurrentOperatingMode == NORMAL) {
             dev.disableAllSensors();
-            // Always use DATA_INJECTION here since this value goes to the HAL and the HAL
-            // doesn't have an understanding of replay vs. normal data injection.
-            status_t err = dev.setMode(DATA_INJECTION);
+            status_t err = NO_ERROR;
+            if (targetOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION) {
+                // Set SensorDevice to HAL_BYPASS_REPLAY_DATA_INJECTION_MODE. This value is not
+                // injected into the HAL, nor will any events be injected into the HAL
+                err = dev.setMode(HAL_BYPASS_REPLAY_DATA_INJECTION);
+            } else {
+                // Otherwise use DATA_INJECTION here since this value goes to the HAL and the HAL
+                // doesn't have an understanding of replay vs. normal data injection.
+                err = dev.setMode(DATA_INJECTION);
+            }
             if (err == NO_ERROR) {
                 mCurrentOperatingMode = targetOperatingMode;
             }
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index bf43101..b643f6b 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -122,6 +122,11 @@
        // delivered to all requesting apps rather than just the package allowed to inject data.
        // This mode is only allowed to be used on development builds.
        REPLAY_DATA_INJECTION = 3,
+       // Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+       // buffer in SensorDevice and played back to SensorService when SensorDevice::poll() is
+       // called. This is useful for playing back sensor data on the platform without relying on
+       // the HAL to support data injection.
+       HAL_BYPASS_REPLAY_DATA_INJECTION = 4,
 
       // State Transitions supported.
       //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION/REPLAY_DATA_INJECTION
@@ -389,6 +394,8 @@
             const String8& packageName,
             int requestedMode, const String16& opPackageName, const String16& attributionTag);
     virtual int isDataInjectionEnabled();
+    virtual int isReplayDataInjectionEnabled();
+    virtual int isHalBypassReplayDataInjectionEnabled();
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             int deviceId, uint32_t size, int32_t type, int32_t format,
             const native_handle *resource);
@@ -507,6 +514,8 @@
     // Removes the capped rate on active direct connections (when the mic toggle is flipped to off)
     void uncapRates();
 
+    bool isInjectionMode(int mode);
+
     static inline bool isAudioServerOrSystemServerUid(uid_t uid) {
         return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER;
     }
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index 6870d4e..f6f104e 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -17,6 +17,7 @@
     static_libs: [
         "libsensorserviceaidl",
         "libpermission",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
         "android.frameworks.sensorservice-V1-ndk",
         "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V2-ndk",
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index dcc29b9..1b1307b 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -366,6 +366,11 @@
     mRenderRate = renderRate;
 }
 
+Fps SurfaceFrame::getRenderRate() const {
+    std::lock_guard<std::mutex> lock(mMutex);
+    return mRenderRate ? *mRenderRate : mDisplayFrameRenderRate;
+}
+
 void SurfaceFrame::setGpuComposition() {
     std::scoped_lock lock(mMutex);
     mGpuComposition = true;
@@ -611,9 +616,11 @@
 }
 
 void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
-                             nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta) {
+                             Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+                             nsecs_t displayPresentDelta) {
     std::scoped_lock lock(mMutex);
 
+    mDisplayFrameRenderRate = displayFrameRenderRate;
     mActuals.presentTime = presentTime;
     nsecs_t deadlineDelta = 0;
 
@@ -837,10 +844,11 @@
     mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
 }
 
-void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate) {
+void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate,
+                                Fps renderRate) {
     ATRACE_CALL();
     std::scoped_lock lock(mMutex);
-    mCurrentDisplayFrame->onSfWakeUp(token, refreshRate,
+    mCurrentDisplayFrame->onSfWakeUp(token, refreshRate, renderRate,
                                      mTokenManager.getPredictionsForToken(token), wakeUpTime);
 }
 
@@ -860,11 +868,12 @@
     mSurfaceFrames.push_back(surfaceFrame);
 }
 
-void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate,
+void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                                              std::optional<TimelineItem> predictions,
                                              nsecs_t wakeUpTime) {
     mToken = token;
     mRefreshRate = refreshRate;
+    mRenderRate = renderRate;
     if (!predictions) {
         mPredictionState = PredictionState::Expired;
     } else {
@@ -1026,7 +1035,8 @@
     classifyJank(deadlineDelta, deltaToVsync, previousPresentTime);
 
     for (auto& surfaceFrame : mSurfaceFrames) {
-        surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, deadlineDelta, deltaToVsync);
+        surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, mRenderRate, deadlineDelta,
+                                deltaToVsync);
     }
 }
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index d54d22d..538ea12 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -183,6 +183,8 @@
     void setDropTime(nsecs_t dropTime);
     void setPresentState(PresentState presentState, nsecs_t lastLatchTime = 0);
     void setRenderRate(Fps renderRate);
+    // Return the render rate if it exists, otherwise returns the DisplayFrame's render rate.
+    Fps getRenderRate() const;
     void setGpuComposition();
 
     // When a bufferless SurfaceFrame is promoted to a buffer SurfaceFrame, we also have to update
@@ -197,7 +199,8 @@
     // displayRefreshRate, displayDeadlineDelta, and displayPresentDelta are propagated from the
     // display frame.
     void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
-                   nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta);
+                   Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+                   nsecs_t displayPresentDelta);
     // All the timestamps are dumped relative to the baseTime
     void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
     // Dumps only the layer, token, is buffer, jank metadata, prediction and present states.
@@ -251,6 +254,8 @@
     int32_t mJankType GUARDED_BY(mMutex) = JankType::None;
     // Indicates if this frame was composited by the GPU or not
     bool mGpuComposition GUARDED_BY(mMutex) = false;
+    // Refresh rate for this frame.
+    Fps mDisplayFrameRenderRate GUARDED_BY(mMutex);
     // Rendering rate for this frame.
     std::optional<Fps> mRenderRate GUARDED_BY(mMutex);
     // Enum for the type of present
@@ -298,7 +303,8 @@
 
     // The first function called by SF for the current DisplayFrame. Fetches SF predictions based on
     // the token and sets the actualSfWakeTime for the current DisplayFrame.
-    virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) = 0;
+    virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate,
+                             Fps renderRate) = 0;
 
     // Sets the sfPresentTime and finalizes the current DisplayFrame. Tracks the
     // given present fence until it's signaled, and updates the present timestamps of all presented
@@ -374,8 +380,8 @@
         // and SYSTEM_TIME_MONOTONIC.
         void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
-        void onSfWakeUp(int64_t token, Fps refreshRate, std::optional<TimelineItem> predictions,
-                        nsecs_t wakeUpTime);
+        void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
+                        std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
         // Sets the appropriate metadata and classifies the jank.
         void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime);
         // Adds the provided SurfaceFrame to the current display frame.
@@ -437,6 +443,8 @@
         // The refresh rate (vsync period) in nanoseconds as seen by SF during this DisplayFrame's
         // timeline
         Fps mRefreshRate;
+        // The current render rate for this DisplayFrame.
+        Fps mRenderRate;
         // TraceCookieCounter is used to obtain the cookie for sendig trace packets to perfetto.
         // Using a reference here because the counter is owned by FrameTimeline, which outlives
         // DisplayFrame.
@@ -453,7 +461,7 @@
             int32_t layerId, std::string layerName, std::string debugName, bool isBuffer,
             GameMode) override;
     void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
-    void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) override;
+    void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 80a51ea..4fd6495 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -84,6 +84,7 @@
     bool isTrustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
+    scheduler::LayerInfo::FrameRate inheritedFrameRate;
     scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
     scheduler::FrameRateCompatibility defaultFrameRateCompatibility =
             scheduler::FrameRateCompatibility::Default;
@@ -122,6 +123,8 @@
         ReachableByRelativeParent
     };
     Reachablilty reachablilty;
+    // True when the surfaceDamage is recognized as a small area update.
+    bool isSmallDirty = false;
 
     static bool isOpaqueFormat(PixelFormat format);
     static bool isTransformValid(const ui::Transform& t);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 03c0993..2a0857d 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -731,6 +731,10 @@
                 : parentSnapshot.outputFilter.layerStack;
     }
 
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
+        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+    }
+
     if (snapshot.isHiddenByPolicyFromParent &&
         !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
         if (forceUpdate ||
@@ -761,10 +765,6 @@
                 (requested.flags & layer_state_t::eLayerSkipScreenshot);
     }
 
-    if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
-        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
-    }
-
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eStretchChanged) {
         snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
                 ? requested.stretchEffect
@@ -815,9 +815,14 @@
                              RequestedLayerState::Changes::Hierarchy)) {
         bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
                 scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
-        snapshot.frameRate = !requested.requestedFrameRate.isValid() || shouldOverrideChildren
-                ? parentSnapshot.frameRate
-                : requested.requestedFrameRate;
+        if (!requested.requestedFrameRate.isValid() || shouldOverrideChildren) {
+            snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
+        } else {
+            snapshot.inheritedFrameRate = requested.requestedFrameRate;
+        }
+        // Set the framerate as the inherited frame rate and allow children to override it if
+        // needed.
+        snapshot.frameRate = snapshot.inheritedFrameRate;
         snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 168267b..0e49b75 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -124,6 +124,7 @@
     dimmingEnabled = true;
     defaultFrameRateCompatibility = static_cast<int8_t>(scheduler::FrameRateCompatibility::Default);
     frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+    frameRateCategorySmoothSwitchOnly = false;
     frameRateSelectionStrategy =
             static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
     dataspace = ui::Dataspace::V0_SRGB;
@@ -208,7 +209,7 @@
                  (barrierFrameNumber > bufferData->frameNumber))) {
                 ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
                       " -> producedId=%d frameNumber=%" PRIu64,
-                      getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+                      getDebugString().c_str(), barrierProducerId, barrierFrameNumber,
                       bufferData->producerId, frameNumber);
                 TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
                                                              /*overwrite=*/false);
@@ -237,8 +238,7 @@
     }
     if (what & (layer_state_t::eAlphaChanged)) {
         if (oldAlpha == 0 || color.a == 0) {
-            changes |= RequestedLayerState::Changes::Visibility |
-                    RequestedLayerState::Changes::VisibleRegion;
+            changes |= RequestedLayerState::Changes::Visibility;
         }
     }
 
@@ -401,6 +401,7 @@
     if (!handleAlive) debug << " !handle";
     if (z != 0) debug << " z=" << z;
     if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
+    debug << "}";
     return debug.str();
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 2dc8758..700baa2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -170,7 +170,6 @@
     mDrawingState.barrierProducerId = 0;
     mDrawingState.bufferTransform = 0;
     mDrawingState.transformToDisplayInverse = false;
-    mDrawingState.crop.makeInvalid();
     mDrawingState.acquireFence = sp<Fence>::make(-1);
     mDrawingState.acquireFenceTime = std::make_shared<FenceTime>(mDrawingState.acquireFence);
     mDrawingState.dataspace = ui::Dataspace::V0_SRGB;
@@ -206,6 +205,7 @@
 
     mOwnerUid = args.ownerUid;
     mOwnerPid = args.ownerPid;
+    mOwnerAppId = mOwnerUid % PER_USER_RANGE;
 
     mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
@@ -787,8 +787,9 @@
         if (includeJankData) {
             std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
                     mPendingJankClassifications.front();
-            jankData.emplace_back(
-                    JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
+            jankData.emplace_back(JankData(surfaceFrame->getToken(),
+                                           surfaceFrame->getJankType().value(),
+                                           surfaceFrame->getRenderRate().getPeriodNsecs()));
         }
         mPendingJankClassifications.pop_front();
     }
@@ -1334,13 +1335,15 @@
     return true;
 }
 
-bool Layer::setFrameRateCategory(FrameRateCategory category) {
-    if (mDrawingState.frameRate.category == category) {
+bool Layer::setFrameRateCategory(FrameRateCategory category, bool smoothSwitchOnly) {
+    if (mDrawingState.frameRate.category == category &&
+        mDrawingState.frameRate.categorySmoothSwitchOnly == smoothSwitchOnly) {
         return false;
     }
 
     mDrawingState.sequence++;
     mDrawingState.frameRate.category = category;
+    mDrawingState.frameRate.categorySmoothSwitchOnly = smoothSwitchOnly;
     mDrawingState.modified = true;
 
     updateTreeHasFrameRateVote();
@@ -2929,17 +2932,14 @@
         }
     }
 
-    // Prevent tracing the same release multiple times.
-    if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
-        mPreviousReleasedFrameNumber = mPreviousFrameNumber;
-    }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
     }
-    mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    if (mBufferInfo.mBuffer) {
+        mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    }
 }
 
 void Layer::onSurfaceFrameCreated(
@@ -3364,7 +3364,7 @@
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
-    setIsSmallDirty();
+    setIsSmallDirty(surfaceDamage, getTransform());
     return true;
 }
 
@@ -4411,7 +4411,9 @@
     mLastLatchTime = latchTime;
 }
 
-void Layer::setIsSmallDirty() {
+void Layer::setIsSmallDirty(const Region& damageRegion,
+                            const ui::Transform& layerToDisplayTransform) {
+    mSmallDirty = false;
     if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
         return;
     }
@@ -4420,17 +4422,26 @@
         mWindowType != WindowInfo::Type::BASE_APPLICATION) {
         return;
     }
-    Rect bounds = mDrawingState.surfaceDamageRegion.getBounds();
+
+    Rect bounds = damageRegion.getBounds();
     if (!bounds.isValid()) {
         return;
     }
 
+    // Transform to screen space.
+    bounds = layerToDisplayTransform.transform(bounds);
+
     // If the damage region is a small dirty, this could give the hint for the layer history that
     // it could suppress the heuristic rate when calculating.
-    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerUid,
+    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
                                                          bounds.getWidth() * bounds.getHeight());
 }
 
+void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
+    setIsSmallDirty(snapshot->surfaceDamage, snapshot->localTransform);
+    snapshot->isSmallDirty = mSmallDirty;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0b66866..dd91adc 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -783,7 +783,7 @@
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
     bool setFrameRate(FrameRate::FrameRateVote);
-    bool setFrameRateCategory(FrameRateCategory);
+    bool setFrameRateCategory(FrameRateCategory, bool smoothSwitchOnly);
 
     bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
 
@@ -837,6 +837,8 @@
 
     pid_t getOwnerPid() { return mOwnerPid; }
 
+    int32_t getOwnerAppId() { return mOwnerAppId; }
+
     // This layer is not a clone, but it's the parent to the cloned hierarchy. The
     // variable mClonedChild represents the top layer that will be cloned so this
     // layer will be the parent of mClonedChild.
@@ -935,7 +937,8 @@
     const sp<SurfaceFlinger> mFlinger;
 
     // Check if the damage region is a small dirty.
-    void setIsSmallDirty();
+    void setIsSmallDirty(const Region& damageRegion, const ui::Transform& layerToDisplayTransform);
+    void setIsSmallDirty(frontend::LayerSnapshot* snapshot);
 
 protected:
     // For unit tests
@@ -1062,6 +1065,8 @@
     // If created from a system process, the value can be passed in.
     pid_t mOwnerPid;
 
+    int32_t mOwnerAppId;
+
     // Keeps track of the time SF latched the last buffer from this layer.
     // Used in buffer stuffing analysis in FrameTimeline.
     nsecs_t mLastLatchTime = 0;
@@ -1071,6 +1076,10 @@
     sp<Fence> mLastClientCompositionFence;
     bool mClearClientCompositionFenceOnLayerDisplayed = false;
 private:
+    // Range of uids allocated for a user.
+    // This value is taken from android.os.UserHandle#PER_USER_RANGE.
+    static constexpr int32_t PER_USER_RANGE = 100000;
+
     friend class SlotGenerationTest;
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
@@ -1227,9 +1236,6 @@
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     sp<IBinder> mPreviousReleaseBufferEndpoint;
-    uint64_t mPreviousReleasedFrameNumber = 0;
-
-    uint64_t mPreviousBarrierFrameNumber = 0;
 
     bool mReleasePreviousBuffer = false;
 
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 48a9190..f25619a 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -247,10 +247,13 @@
     layerSettings.frameNumber = mSnapshot->frameNumber;
     layerSettings.bufferId = mSnapshot->externalTexture->getId();
 
+    const bool useFiltering = targetSettings.needsFiltering ||
+                              mSnapshot->geomLayerTransform.needsBilinearFiltering();
+
     // Query the texture matrix given our current filtering mode.
     float textureMatrix[16];
     getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
-                              mSnapshot->geomBufferTransform, targetSettings.needsFiltering,
+                              mSnapshot->geomBufferTransform, useFiltering,
                               textureMatrix);
 
     if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
@@ -301,7 +304,7 @@
             mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
             mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
 
-    layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering;
+    layerSettings.source.buffer.useTextureFiltering = useFiltering;
     layerSettings.source.buffer.textureTransform =
             mat4(static_cast<const float*>(textureMatrix)) * tr;
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 4e5659e..069d89b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -194,7 +194,8 @@
                                   to_string(vote.fps).c_str(), categoryString.c_str(),
                                   weight * 100);
             summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
-                               vote.seamlessness, vote.category, weight, layerFocused});
+                               vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
+                               weight, layerFocused});
 
             if (CC_UNLIKELY(mTraceEnabled)) {
                 trace(*info, vote.type, vote.fps.getIntValue());
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index dd96930..36f2475 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -309,7 +309,8 @@
             ALOGV("%s uses frame rate category: %d", mName.c_str(),
                   static_cast<int>(mLayerVote.category));
             votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, Fps(),
-                             Seamlessness::Default, mLayerVote.category});
+                             Seamlessness::Default, mLayerVote.category,
+                             mLayerVote.categorySmoothSwitchOnly});
         }
 
         if (mLayerVote.fps.isValid() ||
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index d580b58..7d3cffa 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -71,8 +71,8 @@
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
         Fps fps;
         Seamlessness seamlessness = Seamlessness::Default;
-        // Category is in effect if fps is not specified.
         FrameRateCategory category = FrameRateCategory::Default;
+        bool categorySmoothSwitchOnly = false;
 
         // Returns true if the layer explicitly should contribute to frame rate scoring.
         bool isNoVote() const { return RefreshRateSelector::isNoVote(type); }
@@ -111,6 +111,7 @@
         } vote;
 
         FrameRateCategory category = FrameRateCategory::Default;
+        bool categorySmoothSwitchOnly = false;
 
         FrameRate() = default;
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index b06723d..1d23fb5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -494,6 +494,7 @@
     int explicitExact = 0;
     int explicitCategoryVoteLayers = 0;
     int seamedFocusedLayers = 0;
+    int categorySmoothSwitchOnlyLayers = 0;
 
     for (const auto& layer : layers) {
         switch (layer.vote) {
@@ -531,6 +532,9 @@
         if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
             seamedFocusedLayers++;
         }
+        if (layer.frameRateCategorySmoothSwitchOnly) {
+            categorySmoothSwitchOnlyLayers++;
+        }
     }
 
     const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
@@ -578,10 +582,17 @@
         return {ranking, kNoSignals};
     }
 
+    const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
+    const DisplayModeId activeModeId = activeMode.getId();
+
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending,
+                                            std::nullopt, [&](FrameRateMode mode) {
+                                                return !smoothSwitchOnly ||
+                                                        mode.modePtr->getId() == activeModeId;
+                                            });
         ATRACE_FORMAT_INSTANT("%s (All layers Min)",
                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
@@ -627,6 +638,14 @@
                 continue;
             }
 
+            if (smoothSwitchOnly && modePtr->getId() != activeModeId) {
+                ALOGV("%s ignores %s because it's non-VRR and smooth switch only."
+                      " Current mode = %s",
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
+                      to_string(activeMode).c_str());
+                continue;
+            }
+
             // Layers with default seamlessness vote for the current mode group if
             // there are layers with seamlessness=SeamedAndSeamless and for the default
             // mode group otherwise. In second case, if the current mode group is different
@@ -770,6 +789,7 @@
                                   to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
+            ALOGV("primaryRangeIsSingleRate");
             ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
                                   to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
@@ -805,6 +825,7 @@
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
+        ALOGV("preferredDisplayMode");
         const auto ascendingWithPreferred =
                 rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
         ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
@@ -812,6 +833,7 @@
         return {ascendingWithPreferred, kNoSignals};
     }
 
+    ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
     ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
@@ -1017,7 +1039,8 @@
 
 auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
                                          RefreshRateOrder refreshRateOrder,
-                                         std::optional<DisplayModeId> preferredDisplayModeOpt) const
+                                         std::optional<DisplayModeId> preferredDisplayModeOpt,
+                                         const RankFrameRatesPredicate& predicate) const
         -> FrameRateRanking {
     using fps_approx_ops::operator<;
     const char* const whence = __func__;
@@ -1044,7 +1067,8 @@
     std::deque<ScoredFrameRate> ranking;
     const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
         const auto& modePtr = frameRateMode.modePtr;
-        if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
+        if ((anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) ||
+            !predicate(frameRateMode)) {
             return;
         }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 5d32414..545b939 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -170,8 +170,11 @@
         Fps desiredRefreshRate;
         // If a seamless mode switch is required.
         Seamlessness seamlessness = Seamlessness::Default;
-        // Layer frame rate category. Superseded by desiredRefreshRate.
+        // Layer frame rate category.
         FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        // Goes together with frame rate category vote. Allow refresh rate changes only
+        // if there would be no jank.
+        bool frameRateCategorySmoothSwitchOnly = false;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
         // would have on choosing the refresh rate.
         float weight = 0.0f;
@@ -446,10 +449,15 @@
         ftl_last = Descending
     };
 
-    // Only uses the primary range, not the app request range.
+    typedef std::function<bool(const FrameRateMode)> RankFrameRatesPredicate;
+
+    // Rank the frame rates.
+    // Only modes in the primary range for which `predicate` is `true` will be scored.
+    // Does not use the app requested range.
     FrameRateRanking rankFrameRates(
             std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt) const
+            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt,
+            const RankFrameRatesPredicate& predicate = [](FrameRateMode) { return true; }) const
             REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 5115c42..5b36a5e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -1198,18 +1198,19 @@
 }
 
 void Scheduler::updateSmallAreaDetection(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+        std::vector<std::pair<int32_t, float>>& uidThresholdMappings) {
     mSmallAreaDetectionAllowMappings.update(uidThresholdMappings);
 }
 
-void Scheduler::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
-    mSmallAreaDetectionAllowMappings.setThesholdForUid(uid, threshold);
+void Scheduler::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+    mSmallAreaDetectionAllowMappings.setThesholdForAppId(appId, threshold);
 }
 
-bool Scheduler::isSmallDirtyArea(uid_t uid, uint32_t dirtyArea) {
-    std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForUid(uid);
-    if (oThreshold) return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
-
+bool Scheduler::isSmallDirtyArea(int32_t appId, uint32_t dirtyArea) {
+    std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForAppId(appId);
+    if (oThreshold) {
+        return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
+    }
     return false;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 3441318..b0520a6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -293,12 +293,12 @@
 
     void setGameModeRefreshRateForUid(FrameRateOverride);
 
-    void updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+    void updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
-    void setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    void setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     // Returns true if the dirty area is less than threshold.
-    bool isSmallDirtyArea(uid_t uid, uint32_t dirtyArea);
+    bool isSmallDirtyArea(int32_t appId, uint32_t dirtyArea);
 
     // Retrieves the overridden refresh rate for a given uid.
     std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
index 95cd5d1..38c6da4 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
@@ -19,26 +19,26 @@
 
 namespace android::scheduler {
 void SmallAreaDetectionAllowMappings::update(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+        std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
     std::lock_guard lock(mLock);
     mMap.clear();
-    for (std::pair<uid_t, float> row : uidThresholdMappings) {
+    for (std::pair<int32_t, float> row : appIdThresholdMappings) {
         if (!isValidThreshold(row.second)) continue;
 
         mMap.emplace(row.first, row.second);
     }
 }
 
-void SmallAreaDetectionAllowMappings::setThesholdForUid(uid_t uid, float threshold) {
+void SmallAreaDetectionAllowMappings::setThesholdForAppId(int32_t appId, float threshold) {
     if (!isValidThreshold(threshold)) return;
 
     std::lock_guard lock(mLock);
-    mMap.emplace(uid, threshold);
+    mMap.emplace(appId, threshold);
 }
 
-std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForUid(uid_t uid) {
+std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForAppId(int32_t appId) {
     std::lock_guard lock(mLock);
-    const auto iter = mMap.find(uid);
+    const auto iter = mMap.find(appId);
     if (iter != mMap.end()) {
         return iter->second;
     }
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
index cbab690..e10301c 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
@@ -24,16 +24,16 @@
 
 namespace android::scheduler {
 class SmallAreaDetectionAllowMappings {
-    using UidThresholdMap = std::unordered_map<uid_t, float>;
+    using AppIdThresholdMap = std::unordered_map<int32_t, float>;
 
 public:
-    void update(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
-    void setThesholdForUid(uid_t uid, float threshold) EXCLUDES(mLock);
-    std::optional<float> getThresholdForUid(uid_t uid) EXCLUDES(mLock);
+    void update(std::vector<std::pair<int32_t, float>>& appIdThresholdMappings);
+    void setThesholdForAppId(int32_t appId, float threshold) EXCLUDES(mLock);
+    std::optional<float> getThresholdForAppId(int32_t uid) EXCLUDES(mLock);
 
 private:
     static bool isValidThreshold(float threshold) { return threshold >= 0.0f && threshold <= 1.0f; }
     mutable std::mutex mLock;
-    UidThresholdMap mMap GUARDED_BY(mLock);
+    AppIdThresholdMap mMap GUARDED_BY(mLock);
 };
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index c4c9fa5..f467670 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -40,16 +40,18 @@
 
 namespace {
 
-nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
+nsecs_t getExpectedCallbackTime(nsecs_t now, nsecs_t nextVsyncTime,
                                 const VSyncDispatch::ScheduleTiming& timing) {
-    return nextVsyncTime - timing.readyDuration - timing.workDuration;
+    const auto expectedCallbackTime = nextVsyncTime - timing.readyDuration - timing.workDuration;
+    const auto baseTime = flags::dont_skip_on_early() ? now : expectedCallbackTime;
+    return std::max(baseTime, expectedCallbackTime);
 }
 
 nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
                                 const VSyncDispatch::ScheduleTiming& timing) {
     const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
             std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
-    return getExpectedCallbackTime(nextVsyncTime, timing);
+    return getExpectedCallbackTime(now, nextVsyncTime, timing);
 }
 
 } // namespace
@@ -105,11 +107,11 @@
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
     if (flags::dont_skip_on_early()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
-            return getExpectedCallbackTime(mArmedInfo->mActualVsyncTime, timing);
+            return getExpectedCallbackTime(now, mArmedInfo->mActualVsyncTime, timing);
         }
     } else {
         if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
-            return getExpectedCallbackTime(nextVsyncTime, timing);
+            return getExpectedCallbackTime(now, nextVsyncTime, timing);
         }
     }
 
@@ -119,7 +121,7 @@
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
-    return getExpectedCallbackTime(nextVsyncTime, timing);
+    return getExpectedCallbackTime(now, nextVsyncTime, timing);
 }
 
 void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
index 58ad6cb..67f7abe 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
@@ -60,6 +60,7 @@
     void reset() EXCLUDES(mMutex);
     void cleanup() REQUIRES(mMutex);
     void setDebugState(DebugState) EXCLUDES(mMutex);
+    void setCallback(std::function<void()>&&) REQUIRES(mMutex);
 
     int mTimerFd = -1;
 
@@ -71,6 +72,7 @@
     void endDispatch();
 
     mutable std::mutex mMutex;
+
     std::function<void()> mCallback GUARDED_BY(mMutex);
     bool mExpectingCallback GUARDED_BY(mMutex) = false;
     DebugState mDebugState GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index a4cf57f..09e8a1e 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -93,8 +93,8 @@
         close(mPipes[kWritePipe]);
         mPipes[kWritePipe] = -1;
     }
-    mExpectingCallback = false;
-    mCallback = {};
+
+    setCallback({});
 }
 
 void Timer::endDispatch() {
@@ -112,8 +112,7 @@
     static constexpr int ns_per_s =
             std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
 
-    mCallback = std::move(callback);
-    mExpectingCallback = true;
+    setCallback(std::move(callback));
 
     struct itimerspec old_timer;
     struct itimerspec new_timer {
@@ -142,6 +141,8 @@
     if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
         ALOGW("Failed to disarm timerfd");
     }
+
+    setCallback({});
 }
 
 void Timer::threadMain() {
@@ -231,6 +232,11 @@
     mDebugState = state;
 }
 
+void Timer::setCallback(std::function<void()>&& callback) {
+    mExpectingCallback = bool(callback);
+    mCallback = std::move(callback);
+}
+
 void Timer::dump(std::string& result) const {
     std::lock_guard lock(mMutex);
     result.append("\t\tDebugState: ");
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 7e799bb..9b9a67a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -864,14 +864,14 @@
 
     mPowerAdvisor->init();
 
-    char primeShaderCache[PROPERTY_VALUE_MAX];
-    property_get("service.sf.prime_shader_cache", primeShaderCache, "1");
-    if (atoi(primeShaderCache)) {
+    if (base::GetBoolProperty("service.sf.prime_shader_cache"s, true)) {
         if (setSchedFifo(false) != NO_ERROR) {
             ALOGW("Can't set SCHED_OTHER for primeCache");
         }
 
-        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache();
+        bool shouldPrimeUltraHDR =
+                base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
 
         if (setSchedFifo(true) != NO_ERROR) {
             ALOGW("Can't set SCHED_OTHER for primeCache");
@@ -1047,6 +1047,7 @@
 
         const auto peakFps = mode->getPeakFps();
         outMode.refreshRate = peakFps.getValue();
+        outMode.vsyncRate = mode->getVsyncRate().getValue();
 
         const auto vsyncConfigSet =
                 mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
@@ -1231,7 +1232,7 @@
 }
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
-                                                   DisplayModeId modeId) {
+                                                   DisplayModeId modeId, Fps minFps, Fps maxFps) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -1264,13 +1265,15 @@
         }
 
         const Fps fps = *fpsOpt;
+        const FpsRange physical = {fps, fps};
+        const FpsRange render = {minFps.isValid() ? minFps : fps, maxFps.isValid() ? maxFps : fps};
+        const FpsRanges ranges = {physical, render};
 
         // Keep the old switching type.
         const bool allowGroupSwitching =
                 display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
 
-        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
-                                                                          {fps, fps},
+        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, ranges, ranges,
                                                                           allowGroupSwitching};
 
         return setDesiredDisplayModeSpecsInternal(display, policy);
@@ -2210,8 +2213,29 @@
             continue;
         }
 
-        if (!snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) &&
-            (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) == 0) {
+        const bool updateSmallDirty = mScheduler->supportSmallDirtyDetection() &&
+                ((snapshot->clientChanges & layer_state_t::eSurfaceDamageRegionChanged) ||
+                 snapshot->changes.any(Changes::Geometry));
+
+        const bool hasChanges =
+                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) ||
+                (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
+                        0;
+
+        if (!updateSmallDirty && !hasChanges) {
+            continue;
+        }
+
+        auto it = mLegacyLayers.find(snapshot->sequence);
+        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldn't find layer object for %s",
+                            snapshot->getDebugString().c_str());
+
+        if (updateSmallDirty) {
+            // Update small dirty flag while surface damage region or geometry changed
+            it->second->setIsSmallDirty(snapshot.get());
+        }
+
+        if (!hasChanges) {
             continue;
         }
 
@@ -2221,12 +2245,9 @@
                 .transform = snapshot->geomLayerTransform,
                 .setFrameRateVote = snapshot->frameRate,
                 .frameRateSelectionPriority = snapshot->frameRateSelectionPriority,
+                .isSmallDirty = snapshot->isSmallDirty,
         };
 
-        auto it = mLegacyLayers.find(snapshot->sequence);
-        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                            snapshot->getDebugString().c_str());
-
         if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
             mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
                                                          snapshot->defaultFrameRateCompatibility);
@@ -2400,8 +2421,6 @@
             mLayerLifecycleManager.commitChanges();
         }
 
-        commitTransactions();
-
         // enter boot animation on first buffer latch
         if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
             ALOGI("Enter boot animation");
@@ -2409,6 +2428,10 @@
         }
     }
     mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
+    if (mustComposite && !mLegacyFrontEndEnabled) {
+        commitTransactions();
+    }
+
     return mustComposite;
 }
 
@@ -2499,7 +2522,8 @@
     {
         mFrameTimeline->setSfWakeUp(ftl::to_underlying(vsyncId),
                                     pacesetterFrameTarget.frameBeginTime().ns(),
-                                    Fps::fromPeriodNsecs(vsyncPeriod.ns()));
+                                    Fps::fromPeriodNsecs(vsyncPeriod.ns()),
+                                    mScheduler->getPacesetterRefreshRate());
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
         bool transactionsAreEmpty;
@@ -4027,7 +4051,7 @@
 
     if (sysprop::use_content_detection_for_refresh_rate(false)) {
         features |= Feature::kContentDetection;
-        if (flags::vrr_small_dirty_detection()) {
+        if (flags::enable_small_area_detection()) {
             features |= Feature::kSmallDirtyContentDetection;
         }
     }
@@ -5228,7 +5252,7 @@
     }
     if (what & layer_state_t::eFrameRateCategoryChanged) {
         const FrameRateCategory category = Layer::FrameRate::convertCategory(s.frameRateCategory);
-        if (layer->setFrameRateCategory(category)) {
+        if (layer->setFrameRateCategory(category, s.frameRateCategorySmoothSwitchOnly)) {
             flags |= eTraversalNeeded;
         }
     }
@@ -5668,7 +5692,6 @@
     state.id = transactionId;
 
     // reset screen orientation and use primary layer stack
-    Vector<DisplayState> displays;
     DisplayState d;
     d.what = DisplayState::eDisplayProjectionChanged |
              DisplayState::eLayerStackChanged;
@@ -6929,6 +6952,12 @@
                 return NO_ERROR;
             }
             case 1035: {
+                // Parameters:
+                // - (required) i32 mode id.
+                // - (optional) i64 display id. Using default display if not provided.
+                // - (optional) f min render rate. Using mode's fps is not provided.
+                // - (optional) f max render rate. Using mode's fps is not provided.
+
                 const int modeId = data.readInt32();
 
                 const auto display = [&]() -> sp<IBinder> {
@@ -6945,8 +6974,21 @@
                     return nullptr;
                 }();
 
+                const auto getFps = [&] {
+                    float value;
+                    if (data.readFloat(&value) == NO_ERROR) {
+                        return Fps::fromValue(value);
+                    }
+
+                    return Fps();
+                };
+
+                const auto minFps = getFps();
+                const auto maxFps = getFps();
+
                 mDebugDisplayModeSetByBackdoor = false;
-                const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId});
+                const status_t result =
+                        setActiveModeFromBackdoor(display, DisplayModeId{modeId}, minFps, maxFps);
                 mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
                 return result;
             }
@@ -7059,7 +7101,7 @@
                 if (mTransactionTracing) {
                     int arg = data.readInt32();
                     if (arg == -1) {
-                        mTransactionTracing.reset();
+                        mScheduler->schedule([&]() { mTransactionTracing.reset(); }).get();
                     } else if (arg > 0) {
                         // Transaction tracing is always running but allow the user to temporarily
                         // increase the buffer when actively debugging.
@@ -8342,13 +8384,13 @@
 }
 
 status_t SurfaceFlinger::updateSmallAreaDetection(
-        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
-    mScheduler->updateSmallAreaDetection(uidThresholdMappings);
+        std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
+    mScheduler->updateSmallAreaDetection(appIdThresholdMappings);
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
-    mScheduler->setSmallAreaDetectionThreshold(uid, threshold);
+status_t SurfaceFlinger::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+    mScheduler->setSmallAreaDetectionThreshold(appId, threshold);
     return NO_ERROR;
 }
 
@@ -8489,15 +8531,6 @@
 void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) {
     mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight());
     getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize());
-
-    // Notify layers to update small dirty flag.
-    if (mScheduler->supportSmallDirtyDetection()) {
-        mCurrentState.traverse([&](Layer* layer) {
-            if (layer->getLayerStack() == activeDisplay.getLayerStack()) {
-                layer->setIsSmallDirty();
-            }
-        });
-    }
 }
 
 sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const {
@@ -9189,6 +9222,7 @@
         outMode.xDpi = mode.xDpi;
         outMode.yDpi = mode.yDpi;
         outMode.refreshRate = mode.refreshRate;
+        outMode.vsyncRate = mode.vsyncRate;
         outMode.appVsyncOffset = mode.appVsyncOffset;
         outMode.sfVsyncOffset = mode.sfVsyncOffset;
         outMode.presentationDeadline = mode.presentationDeadline;
@@ -9719,18 +9753,18 @@
     return binder::Status::ok();
 }
 
-binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& uids,
+binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                                              const std::vector<float>& thresholds) {
     status_t status;
     const int c_uid = IPCThreadState::self()->getCallingUid();
     if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
-        if (uids.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
+        if (appIds.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
 
-        std::vector<std::pair<uid_t, float>> mappings;
-        const size_t size = uids.size();
+        std::vector<std::pair<int32_t, float>> mappings;
+        const size_t size = appIds.size();
         mappings.reserve(size);
         for (int i = 0; i < size; i++) {
-            auto row = std::make_pair(static_cast<uid_t>(uids[i]), thresholds[i]);
+            auto row = std::make_pair(appIds[i], thresholds[i]);
             mappings.push_back(row);
         }
         status = mFlinger->updateSmallAreaDetection(mappings);
@@ -9741,11 +9775,11 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t uid, float threshold) {
+binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
     status_t status;
     const int c_uid = IPCThreadState::self()->getCallingUid();
     if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
-        status = mFlinger->setSmallAreaDetectionThreshold(uid, threshold);
+        status = mFlinger->setSmallAreaDetectionThreshold(appId, threshold);
     } else {
         ALOGE("setSmallAreaDetectionThreshold() permission denied for uid: %d", c_uid);
         status = PERMISSION_DENIED;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index cbea312..96b67b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -606,9 +606,9 @@
 
     status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
-    status_t updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+    status_t updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
-    status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+    status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
 
     int getGpuContextPriority();
 
@@ -684,7 +684,8 @@
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
 
-    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
+    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
+                                       Fps maxFps);
 
     void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
     void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
@@ -728,8 +729,10 @@
             compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
                                           const std::vector<std::pair<Layer*, LayerFE*>>& layers);
+    // Return true if we must composite this frame
     bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                                     bool& out) REQUIRES(kMainThreadContext);
+    // Return true if we must composite this frame
     bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
     void updateLayerHistory(nsecs_t now);
@@ -1572,9 +1575,9 @@
     binder::Status setDebugFlash(int delay) override;
     binder::Status scheduleComposite() override;
     binder::Status scheduleCommit() override;
-    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& uids,
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                             const std::vector<float>& thresholds) override;
-    binder::Status setSmallAreaDetectionThreshold(int32_t uid, float threshold) override;
+    binder::Status setSmallAreaDetectionThreshold(int32_t appId, float threshold) override;
     binder::Status getGpuContextPriority(int32_t* outPriority) override;
     binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override;
     binder::Status addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener,
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 922fd07..f339d22 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -40,5 +40,10 @@
     {
       "name": "libsurfaceflinger_unittest"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsSurfaceControlTestsStaging"
+    }
   ]
 }
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index 698ef06..18022b1 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -64,8 +64,17 @@
 
     if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing,
                                         generateLastEntryOnly)) {
-        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
+        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath << "\n";
         return -1;
     }
+
+    // Set output file permissions (-rw-r--r--)
+    outStream.close();
+    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+    if (chmod(outputLayersTracePath, mode) != 0) {
+        std::cout << "Error: Failed to set permissions of " << outputLayersTracePath << "\n";
+        return -1;
+    }
+
     return 0;
 }
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 5a277bd..19d194f 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -32,14 +32,6 @@
 }
 
 flag {
-  name: "vrr_small_dirty_detection"
-  namespace: "core_graphics"
-  description: "Controls small dirty detection for VRR"
-  bug: "283055450"
-  is_fixed_read_only: true
-}
-
-flag {
   name: "dont_skip_on_early"
   namespace: "core_graphics"
   description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
@@ -53,3 +45,11 @@
   bug: "259132483"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "enable_small_area_detection"
+  namespace: "core_graphics"
+  description: "Feature flag for SmallAreaDetection"
+  bug: "283055450"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
index f2874ae..4f41a81 100644
--- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -28,33 +28,95 @@
 
 using gui::aidl_utils::statusTFromBinderStatus;
 
-TEST(BootDisplayModeTest, setBootDisplayMode) {
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+struct BootDisplayModeTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        mSf = ComposerServiceAIDL::getComposerService();
 
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    bool bootModeSupport = false;
-    binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
-    ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
-    if (bootModeSupport) {
-        status = sf->setBootDisplayMode(displayToken, 0);
+        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        ASSERT_FALSE(ids.empty());
+        mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        bool bootModeSupport = false;
+        binder::Status status = mSf->getBootDisplayModeSupport(&bootModeSupport);
+        ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
+
+        if (!bootModeSupport) {
+            GTEST_SKIP() << "Boot mode not supported";
+        }
+
+        gui::DynamicDisplayInfo info;
+        status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
         ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+        mOldMode = info.preferredBootDisplayMode;
+        const auto newMode = [&]() -> std::optional<ui::DisplayModeId> {
+            for (const auto& mode : info.supportedDisplayModes) {
+                if (mode.id != mOldMode) {
+                    return std::optional(mode.id);
+                }
+            }
+            return std::nullopt;
+        }();
+
+        if (!newMode) {
+            GTEST_SKIP() << "Only a single mode is supported";
+        }
+
+        mNewMode = *newMode;
     }
+
+    void TearDown() override {
+        binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mOldMode);
+        EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+        gui::DynamicDisplayInfo info;
+        status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+        EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+        EXPECT_EQ(mOldMode, info.preferredBootDisplayMode);
+    }
+
+    ui::DisplayModeId mOldMode;
+    ui::DisplayModeId mNewMode;
+    sp<gui::ISurfaceComposer> mSf;
+    sp<IBinder> mDisplayToken;
+};
+
+TEST_F(BootDisplayModeTest, setBootDisplayMode) {
+    // Set a new mode and check that it got applied
+    binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mNewMode);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    gui::DynamicDisplayInfo info;
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(mNewMode, info.preferredBootDisplayMode);
 }
 
-TEST(BootDisplayModeTest, clearBootDisplayMode) {
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    bool bootModeSupport = false;
-    binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
-    ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
-    if (bootModeSupport) {
-        status = sf->clearBootDisplayMode(displayToken);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
+TEST_F(BootDisplayModeTest, clearBootDisplayMode) {
+    // Clear once to figure out what the system default is
+    binder::Status status = mSf->clearBootDisplayMode(mDisplayToken);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    gui::DynamicDisplayInfo info;
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    const ui::DisplayModeId systemMode = info.preferredBootDisplayMode;
+    const ui::DisplayModeId newMode = systemMode == mOldMode ? mNewMode : mOldMode;
+
+    // Now set a new mode and clear the boot mode again to figure out if the api worked.
+    status = mSf->setBootDisplayMode(mDisplayToken, newMode);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(newMode, info.preferredBootDisplayMode);
+
+    status = mSf->clearBootDisplayMode(mDisplayToken);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+    status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+    EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+    EXPECT_EQ(systemMode, info.preferredBootDisplayMode);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 69e9a16..822ac4d 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -275,18 +275,6 @@
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
 }
 
-TEST_F(CredentialsTest, CaptureTest) {
-    const auto display = getFirstDisplayToken();
-    std::function<status_t()> condition = [=]() {
-        sp<GraphicBuffer> outBuffer;
-        DisplayCaptureArgs captureArgs;
-        captureArgs.displayToken = display;
-        ScreenCaptureResults captureResults;
-        return ScreenCapture::captureDisplay(captureArgs, captureResults);
-    };
-    ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
-}
-
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
diff --git a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
index 34c9182..f9b4bba 100644
--- a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
@@ -167,18 +167,18 @@
             .setFlags(layer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
             .apply(true);
 
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
+    LayerCaptureArgs args;
+    args.layerHandle = layer->getHandle();
 
     ScreenCaptureResults captureResults;
     {
         // Ensure the UID is not root because root has all permissions
         UIDFaker f(AID_APP_START);
-        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(args, captureResults));
+        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(args, captureResults));
     }
 
     Transaction().setFlags(layer, 0, layer_state_t::eLayerSecure).apply(true);
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, captureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(args, captureResults));
 }
 
 TEST_P(LayerTypeTransactionTest, RefreshRateIsInitialized) {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 91910c7..5a3bca1 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -45,8 +45,7 @@
 cc_aconfig_library {
     name: "libsurfaceflingerflags_test",
     aconfig_declarations: "surfaceflinger_flags",
-    // TODO(b/304338314): uncomment the below line once the bug is fixed
-    // test: true,
+    test: true,
 }
 
 cc_test {
@@ -64,6 +63,7 @@
         "libsurfaceflinger_unittest_main.cpp",
         "ActiveDisplayRotationFlagsTest.cpp",
         "BackgroundExecutorTest.cpp",
+        "CommitTest.cpp",
         "CompositionTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
new file mode 100644
index 0000000..df53d19
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CommitTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <renderengine/mock/RenderEngine.h>
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class CommitTest : public testing::Test {
+protected:
+    CommitTest() {
+        mFlinger.setupMockScheduler();
+        mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    }
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+};
+
+namespace {
+
+TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/0, unused);
+    EXPECT_FALSE(mustComposite);
+}
+
+// Ensure that we handle eTransactionNeeded correctly
+TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+    // update display level color matrix
+    mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/0, unused);
+    EXPECT_TRUE(mustComposite);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 8911430..636d852 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -54,6 +54,8 @@
 constexpr int32_t sLayerIdOne = 1;
 constexpr int32_t sLayerIdTwo = 2;
 constexpr GameMode sGameMode = GameMode::Unsupported;
+constexpr Fps RR_11 = Fps::fromPeriodNsecs(11);
+constexpr Fps RR_30 = Fps::fromPeriodNsecs(30);
 
 class FrameTimelineTest : public testing::Test {
 public:
@@ -276,7 +278,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setDropTime(12);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -309,7 +311,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo,
                                                        sLayerNameTwo, sLayerNameTwo,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -354,7 +356,7 @@
                 mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
-        mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -379,7 +381,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
     mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -422,7 +424,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -439,7 +441,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -456,7 +458,7 @@
                                                            sLayerNameOne, sLayerNameOne,
                                                            /*isBuffer*/ true, sGameMode);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
-        mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+        mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
         mFrameTimeline->setSfPresent(27, presentFence);
@@ -465,7 +467,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
@@ -478,7 +480,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -496,7 +498,7 @@
 
 // Tests related to TimeStats
 TEST_F(FrameTimelineTest, presentFenceSignaled_doesNotReportForInvalidTokens) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(0);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken1 = -1;
@@ -509,7 +511,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -519,7 +521,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -537,7 +539,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -547,7 +549,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps refreshRate = RR_11;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -566,7 +568,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -577,7 +579,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
-    Fps refreshRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_30;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                                 sLayerNameOne, sGameMode,
@@ -594,7 +596,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setAcquireFenceTime(20);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -622,7 +624,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(45);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -651,7 +653,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -680,7 +682,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -709,7 +711,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented,
                                    /*previousLatchTime*/ 56);
@@ -721,8 +723,8 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
-    Fps renderRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_11;
+    Fps renderRate = RR_30;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne,
                                                                 sLayerNameOne, sGameMode,
@@ -740,7 +742,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(45);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
@@ -752,8 +754,8 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
-    Fps refreshRate = Fps::fromPeriodNsecs(11);
-    Fps renderRate = Fps::fromPeriodNsecs(30);
+    Fps refreshRate = RR_11;
+    Fps renderRate = RR_30;
 
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
@@ -775,7 +777,7 @@
     surfaceFrame1->setAcquireFenceTime(45);
     // Trigger a prediction expiry
     flushTokens();
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
@@ -814,7 +816,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -844,7 +846,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token2, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token2, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -866,7 +868,7 @@
     tracingSession->StartBlocking();
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(-1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(-1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(25, presentFence1);
     presentFence1->signalForTest(30);
 
@@ -890,7 +892,7 @@
                                                        /*isBuffer*/ true, sGameMode);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(25, presentFence1);
@@ -1068,7 +1070,7 @@
     int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, presentFence1);
     presentFence1->signalForTest(31);
 
@@ -1150,7 +1152,7 @@
     addEmptySurfaceFrame();
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, presentFence1);
     presentFence1->signalForTest(31);
 
@@ -1252,7 +1254,7 @@
     auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1398,7 +1400,7 @@
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(sfEndTime, presentFence1);
@@ -1475,7 +1477,7 @@
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
-    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
     surfaceFrame1->setDropTime(sfStartTime);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1528,7 +1530,7 @@
             mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
     mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1552,11 +1554,11 @@
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
-    Fps vsyncRate = Fps::fromPeriodNsecs(11);
+    Fps vsyncRate = RR_11;
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
     presentFence1->signalForTest(30);
@@ -1566,7 +1568,7 @@
 
     // Trigger a flush by finalizing the next DisplayFrame
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(56, presentFence2);
     displayFrame = getDisplayFrame(0);
 
@@ -1591,11 +1593,11 @@
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
-    Fps vsyncRate = Fps::fromPeriodNsecs(11);
+    Fps vsyncRate = RR_11;
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
     presentFence1->signalForTest(50);
@@ -1605,7 +1607,7 @@
 
     // Trigger a flush by finalizing the next DisplayFrame
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
     mFrameTimeline->setSfPresent(56, presentFence2);
     displayFrame = getDisplayFrame(0);
 
@@ -1633,7 +1635,7 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent) {
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({12, 18, 40});
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
 
     mFrameTimeline->setSfPresent(22, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1673,7 +1675,7 @@
     int64_t sfToken4 = mTokenManager->generateTokenForPredictions({112, 120, 120});
 
     // case 1 - cpu time = 33 - 12 = 21, vsync period = 11
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
     mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
     auto displayFrame0 = getDisplayFrame(0);
     gpuFence1->signalForTest(36);
@@ -1683,7 +1685,7 @@
     EXPECT_EQ(displayFrame0->getActuals().presentTime, 0);
 
     // case 2 - cpu time = 56 - 52 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
     mFrameTimeline->setSfPresent(56, presentFence2, gpuFence2);
     auto displayFrame1 = getDisplayFrame(1);
     gpuFence2->signalForTest(76);
@@ -1697,7 +1699,7 @@
     EXPECT_EQ(displayFrame0->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // case 3 - cpu time = 86 - 82 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken3, 106, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken3, 106, RR_30, RR_30);
     mFrameTimeline->setSfPresent(112, presentFence3, gpuFence3);
     auto displayFrame2 = getDisplayFrame(2);
     gpuFence3->signalForTest(116);
@@ -1711,7 +1713,7 @@
     EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // case 4 - cpu time = 86 - 82 = 4, vsync period = 30
-    mFrameTimeline->setSfWakeUp(sfToken4, 120, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken4, 120, RR_30, RR_30);
     mFrameTimeline->setSfPresent(140, presentFence4, gpuFence4);
     auto displayFrame3 = getDisplayFrame(3);
     gpuFence4->signalForTest(156);
@@ -1748,7 +1750,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(16);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(27, presentFence1);
@@ -1771,7 +1773,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(36);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1799,10 +1801,10 @@
     ::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
 
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
-                                                   sLayerNameOne, sGameMode,
-                                                   JankType::PredictionError, -3, 5, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+                                                                sLayerNameOne, sGameMode,
+                                                                JankType::PredictionError, -3, 5,
+                                                                0}));
 
     addEmptyDisplayFrame();
 
@@ -1834,7 +1836,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(16);
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1857,7 +1859,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(36);
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1885,10 +1887,10 @@
     ::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
 
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
-                                                   sLayerNameOne, sGameMode,
-                                                   JankType::PredictionError, -3, 5, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+                                                                sLayerNameOne, sGameMode,
+                                                                JankType::PredictionError, -3, 5,
+                                                                0}));
 
     addEmptyDisplayFrame();
 
@@ -1919,7 +1921,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken1, 42, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 42, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(46, presentFence1);
@@ -1966,7 +1968,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(26);
-    mFrameTimeline->setSfWakeUp(sfToken1, 32, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 32, RR_11, RR_11);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(36, presentFence1);
@@ -1989,7 +1991,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(40);
-    mFrameTimeline->setSfWakeUp(sfToken2, 43, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 43, RR_11, RR_11);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(56, presentFence2);
@@ -2047,7 +2049,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(56, presentFence1);
@@ -2070,7 +2072,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(84);
-    mFrameTimeline->setSfWakeUp(sfToken2, 112, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 112, RR_30, RR_30);
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
     mFrameTimeline->setSfPresent(116, presentFence2);
@@ -2131,7 +2133,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame1->setAcquireFenceTime(50);
-    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
     mFrameTimeline->setSfPresent(56, presentFence1);
@@ -2154,7 +2156,7 @@
                                                        sLayerNameOne, sLayerNameOne,
                                                        /*isBuffer*/ true, sGameMode);
     surfaceFrame2->setAcquireFenceTime(80);
-    mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_30, RR_30);
     // Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
@@ -2206,7 +2208,7 @@
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
 
     // Case 1: cpu time = 33 - 12 = 21, vsync period = 11
-    mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
     mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
     auto displayFrame = getDisplayFrame(0);
     gpuFence1->signalForTest(36);
@@ -2225,7 +2227,7 @@
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
 
     // Case 2: No GPU fence so it will not use GPU composition.
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(30));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
     mFrameTimeline->setSfPresent(66, presentFence2);
     auto displayFrame2 = getDisplayFrame(2); // 2 because of previous empty frame
     presentFence2->signalForTest(90);
@@ -2250,13 +2252,13 @@
     int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
     int64_t sfToken3 = mTokenManager->generateTokenForPredictions({72, 80, 80});
 
-    mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
     mFrameTimeline->setSfPresent(26, erroneousPresentFence1);
 
-    mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
     mFrameTimeline->setSfPresent(60, erroneousPresentFence2);
 
-    mFrameTimeline->setSfWakeUp(sfToken3, 72, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfWakeUp(sfToken3, 72, RR_11, RR_11);
     mFrameTimeline->setSfPresent(80, validPresentFence);
 
     erroneousPresentFence2->signalForTest(2);
@@ -2479,4 +2481,44 @@
     mFrameTimeline->setSfPresent(50, presentFence);
     ASSERT_EQ(surfaceFrame->getBaseTime(), 50);
 }
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingDisplayRate) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = token1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_30, RR_11);
+    surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame);
+    presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+    mFrameTimeline->setSfPresent(50, presentFence1);
+
+    EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 11);
+}
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingAppFrameRate) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = token1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    surfaceFrame->setRenderRate(RR_30);
+    mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
+    surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame);
+    presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+    mFrameTimeline->setSfPresent(50, presentFence1);
+
+    EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 30);
+}
 } // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index d319dcc..48f8923 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -421,6 +421,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setDamageRegion(uint32_t id, const Region& damageRegion) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setDataspace(uint32_t id, ui::Dataspace dataspace) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
@@ -432,6 +443,19 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
+        layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
+
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.matrix = matrix;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setShadowRadius(uint32_t id, float shadowRadius) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
@@ -443,6 +467,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     LayerLifecycleManager mLifecycleManager;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index a462082..631adf1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -866,6 +866,104 @@
     }
 }
 
+class SmallAreaDetectionTest : public LayerHistoryIntegrationTest {
+protected:
+    static constexpr int32_t DISPLAY_WIDTH = 100;
+    static constexpr int32_t DISPLAY_HEIGHT = 100;
+
+    static constexpr int32_t kAppId1 = 10100;
+    static constexpr int32_t kAppId2 = 10101;
+
+    static constexpr float kThreshold1 = 0.05f;
+    static constexpr float kThreshold2 = 0.07f;
+
+    SmallAreaDetectionTest() : LayerHistoryIntegrationTest() {
+        std::vector<std::pair<int32_t, float>> mappings;
+        mappings.reserve(2);
+        mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+        mappings.push_back(std::make_pair(kAppId2, kThreshold2));
+
+        mFlinger.enableNewFrontEnd();
+
+        mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
+        mScheduler->updateSmallAreaDetection(mappings);
+    }
+
+    auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+        std::string layerName = "test layer:" + std::to_string(sequence);
+
+        LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(),
+                                                   nullptr,
+                                                   layerName,
+                                                   0,
+                                                   {},
+                                                   std::make_optional<uint32_t>(sequence)};
+        args.ownerUid = kAppId1;
+        args.metadata.setInt32(gui::METADATA_WINDOW_TYPE, 2); // APPLICATION
+        const auto layer = sp<Layer>::make(args);
+        mFlinger.injectLegacyLayer(layer);
+        createRootLayer(sequence);
+        return layer;
+    }
+};
+
+TEST_F(SmallAreaDetectionTest, SmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(10, 10)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, NotSmallDirtyLayer) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(50, 50)));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, smallDirtyLayerWithMatrix) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // Original damage region is a small dirty.
+    uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+    setBuffer(sequence);
+    setDamageRegion(sequence, Region(Rect(20, 20)));
+    updateLayerSnapshotsAndLayerHistory(time);
+    ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+
+    setMatrix(sequence, 2.0f, 0, 0, 2.0f);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    // Verify if the small dirty is scaled.
+    ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
 INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryIntegrationTestParameterized,
                         ::testing::Values(1s, 2s, 3s, 4s, 5s));
 
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index 1fde9b8..aecfcba 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -525,4 +525,39 @@
     mLifecycleManager.commitChanges();
 }
 
+// Even when it does not change visible region, we should mark alpha changes as affecting
+// visible region because HWC impl depends on it. writeOutputIndependentGeometryStateToHWC
+// is only called if we are updating geometry.
+TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) {
+    mLifecycleManager.commitChanges();
+    float startingAlpha = 0.5f;
+    setAlpha(1, startingAlpha);
+
+    // this is expected because layer alpha changes from 1 to 0.5, it may no longer be opaque
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::AffectsChildren |
+                      RequestedLayerState::Changes::VisibleRegion)
+                      .string());
+    EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(startingAlpha));
+    mLifecycleManager.commitChanges();
+
+    float endingAlpha = 0.2f;
+    setAlpha(1, endingAlpha);
+
+    // this is not expected but we should make sure this behavior does not change
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::AffectsChildren |
+                      RequestedLayerState::Changes::VisibleRegion)
+                      .string());
+    EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(endingAlpha));
+    mLifecycleManager.commitChanges();
+
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+              ftl::Flags<RequestedLayerState::Changes>().string());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 50dfcaa..e784eb7 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -282,17 +282,7 @@
     // │   └── 13
     // └── 2
 
-    std::vector<TransactionState> transactions;
-    transactions.emplace_back();
-    transactions.back().states.push_back({});
-    transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
-    transactions.back().states.front().state.frameRate = 90.0;
-    transactions.back().states.front().state.frameRateCompatibility =
-            ANATIVEWINDOW_FRAME_RATE_EXACT;
-    transactions.back().states.front().state.changeFrameRateStrategy =
-            ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS;
-    transactions.back().states.front().layerId = 11;
-    mLifecycleManager.applyTransactions(transactions);
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
 
     EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
@@ -303,6 +293,42 @@
     EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
 }
 
+TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotesDoesNotAffectSiblings) {
+    // ROOT
+    // ├── 1 (verify layer has no vote)
+    // │   ├── 11 (frame rate set)
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set)
+    // │   │   ├── 121
+    // │   │   └── 122
+    // │   │       └── 1221
+    // │   └── 13 (verify layer has default vote)
+    // └── 2
+
+    setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+    setFrameRate(12, 45.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+    EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(12)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(121)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.rate.getIntValue(), 45);
+    EXPECT_EQ(getSnapshot(1221)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(13)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.rate.getIntValue(), 0);
+    EXPECT_EQ(getSnapshot(2)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+}
+
 TEST_F(LayerSnapshotTest, CanCropTouchableRegion) {
     // ROOT
     // ├── 1
@@ -861,4 +887,15 @@
     EXPECT_EQ(getSnapshot(1)->shadowSettings.length, SHADOW_RADIUS);
 }
 
+TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
+    hideLayer(1);
+    setTrustedOverlay(1, true);
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0b67137..faa12a1 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -117,9 +117,9 @@
         return std::make_pair(ranking, consideredSignals);
     }
 
-    ftl::NonNull<DisplayModePtr> getBestFrameRateMode(
-            const std::vector<LayerRequirement>& layers = {}, GlobalSignals signals = {}) const {
-        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr;
+    FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
+                                       GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode;
     }
 
     ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
@@ -429,11 +429,11 @@
 
         // If there are no layers we select the default frame rate, which is the max of the primary
         // range.
-        EXPECT_EQ(kMode90, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode90, selector.getBestFrameRateMode().modePtr);
 
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-        EXPECT_EQ(kMode60, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode().modePtr);
     }
     {
         // We select max even when this will cause a non-seamless switch.
@@ -442,7 +442,7 @@
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy(
                           {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
-        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode());
+        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode().modePtr);
     }
 }
 
@@ -455,7 +455,7 @@
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
@@ -466,107 +466,107 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.name = "";
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
@@ -577,32 +577,32 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
@@ -612,26 +612,26 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
@@ -645,19 +645,19 @@
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
@@ -673,7 +673,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -681,7 +681,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -689,7 +689,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -697,7 +697,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -705,7 +705,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -713,7 +713,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -721,7 +721,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -729,7 +729,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -737,7 +737,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest,
@@ -756,7 +756,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -764,7 +764,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -772,7 +772,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -780,7 +780,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -788,7 +788,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -796,7 +796,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -804,7 +804,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -812,7 +812,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -820,14 +820,14 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -835,7 +835,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "120Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -843,7 +843,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitExact;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 10_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -851,7 +851,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.desiredRefreshRate = 30_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -862,7 +862,7 @@
     lr3.vote = LayerVoteType::Heuristic;
     lr3.desiredRefreshRate = 120_Hz;
     lr3.name = "120Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
@@ -872,26 +872,26 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
@@ -902,42 +902,42 @@
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr.desiredRefreshRate = 24_Hz;
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
@@ -949,39 +949,39 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 15_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 30_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
@@ -993,7 +993,7 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers);
+        const auto mode = selector.getBestFrameRateMode(layers).modePtr;
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getPeakFps()) << "("
                                  << to_string(mode->getVsyncRate()) << ")";
@@ -1009,7 +1009,7 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers);
+        const auto mode = selector.getBestFrameRateMode(layers).modePtr;
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getPeakFps()) << "("
                                  << to_string(mode->getVsyncRate()) << ")";
@@ -1027,19 +1027,19 @@
     lr1.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
@@ -1051,7 +1051,7 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestFrameRateMode(layers, {});
+        const auto mode = selector.getBestFrameRateMode(layers, {}).modePtr;
         EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getPeakFps()) << "("
                                  << to_string(mode->getVsyncRate()) << ")";
@@ -1071,7 +1071,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
@@ -1079,14 +1079,14 @@
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
@@ -1094,14 +1094,14 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
@@ -1116,28 +1116,28 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1146,7 +1146,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
@@ -1464,7 +1464,8 @@
             layers.push_back(layer);
         }
 
-        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getPeakFps())
+        EXPECT_EQ(testCase.expectedFrameRate,
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
                 << "Did not get expected frame rate for frameRate="
                 << to_string(testCase.desiredFrameRate)
                 << " category=" << ftl::enum_string(testCase.frameRateCategory);
@@ -1528,13 +1529,147 @@
             layers.push_back(layer);
         }
 
-        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getPeakFps())
+        EXPECT_EQ(testCase.expectedFrameRate,
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
                 << "Did not get expected frame rate for frameRate="
                 << to_string(testCase.desiredFrameRate)
                 << " category=" << ftl::enum_string(testCase.frameRateCategory);
     }
 }
 
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+
+    struct Case {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        bool smoothSwitchOnly = false;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId60;
+    };
+
+    const std::initializer_list<Case> testCases = {
+            // These layers may switch modes because smoothSwitchOnly=false.
+            {FrameRateCategory::Default, false, 120_Hz, kModeId120},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
+            {FrameRateCategory::High, false, 120_Hz, kModeId120},
+
+            // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
+            // active mode (120Hz).
+            {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+            {FrameRateCategory::High, true, 120_Hz, kModeId120},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+              ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+               << " smooth:" << testCase.smoothSwitchOnly << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .frameRateCategorySmoothSwitchOnly =
+                                              testCase.smoothSwitchOnly,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+                << "Did not get expected frame rate for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+        EXPECT_EQ(testCase.expectedModeId, actualFrameRateMode.modePtr->getId())
+                << "Did not get expected mode for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    struct Case {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        bool smoothSwitchOnly = false;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Note that `smoothSwitchOnly` should not have an effect.
+    const std::initializer_list<Case> testCases = {
+            {FrameRateCategory::Default, false, 240_Hz},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, false, 240_Hz},
+            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Normal, false, 60_Hz},
+            {FrameRateCategory::High, false, 120_Hz},
+            {FrameRateCategory::Default, true, 240_Hz},
+            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+            {FrameRateCategory::NoPreference, true, 240_Hz},
+            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Normal, true, 60_Hz},
+            {FrameRateCategory::High, true, 120_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+              ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+               << " smooth:" << testCase.smoothSwitchOnly << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .frameRateCategorySmoothSwitchOnly =
+                                              testCase.smoothSwitchOnly,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+                << "Did not get expected frame rate for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+        // Expect all cases to be able to stay at the mode with TE 240 due to VRR compatibility.
+        EXPECT_EQ(kVrrMode120TE240->getId(), actualFrameRateMode.modePtr->getId())
+                << "Did not get expected mode for category="
+                << ftl::enum_string(testCase.frameRateCategory)
+                << " (smooth=" << testCase.smoothSwitchOnly << ")";
+    }
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
     auto selector = createSelector(kModes_60_90_72_120, kModeId60);
 
@@ -1568,7 +1703,7 @@
         ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        const auto bestMode = selector.getBestFrameRateMode(layers);
+        const auto bestMode = selector.getBestFrameRateMode(layers).modePtr;
         EXPECT_EQ(expected, bestMode->getPeakFps())
                 << "expected " << expected << " for " << desired << " but got "
                 << bestMode->getPeakFps() << "(" << bestMode->getVsyncRate() << ")";
@@ -1589,7 +1724,7 @@
         lr.vote = LayerVoteType::ExplicitExactOrMultiple;
         lr.desiredRefreshRate = 23.976_Hz;
         lr.name = "ExplicitExactOrMultiple 23.976 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
@@ -1600,7 +1735,7 @@
 
         lr.desiredRefreshRate = 24_Hz;
         lr.name = "ExplicitExactOrMultiple 24 Hz";
-        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 29.97 will prefer 59.94 over 60 and 30
@@ -1611,7 +1746,7 @@
 
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 29.97 will choose 60 if 59.94 is not supported
@@ -1620,7 +1755,7 @@
 
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 
     // Test that 59.94 will choose 60 if 59.94 is not supported
@@ -1629,7 +1764,7 @@
 
         lr.desiredRefreshRate = 59.94_Hz;
         lr.name = "ExplicitExactOrMultiple 59.94 Hz";
-        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+        EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
     }
 }
 
@@ -1648,7 +1783,8 @@
             ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getPeakFps());
+            EXPECT_EQ(lr.desiredRefreshRate,
+                      selector.getBestFrameRateMode(layers).modePtr->getPeakFps());
         }
     }
 }
@@ -1694,7 +1830,7 @@
     lr.desiredRefreshRate = 90_Hz;
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
@@ -1905,46 +2041,46 @@
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::ExplicitDefault;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Heuristic;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Max;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Max";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.vote = LayerVoteType::Min;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Min";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
@@ -1960,7 +2096,7 @@
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
@@ -1978,7 +2114,7 @@
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
@@ -1997,7 +2133,7 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
@@ -2018,7 +2154,7 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
@@ -2042,7 +2178,7 @@
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
@@ -2071,7 +2207,7 @@
     layers[1].name = "90Hz ExplicitDefault";
     layers[1].focused = false;
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
@@ -2104,7 +2240,7 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
@@ -2134,7 +2270,7 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
@@ -2154,10 +2290,10 @@
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
 
     selector.setActiveMode(kModeId120, 120_Hz);
-    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
@@ -2182,14 +2318,14 @@
                                              .weight = 1.f,
                                              .focused = true}};
 
-    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers).modePtr->getId());
 
     auto& seamedLayer = layers[0];
     seamedLayer.desiredRefreshRate = 30_Hz;
     seamedLayer.name = "30Hz ExplicitDefault";
     selector.setActiveMode(kModeId30, 30_Hz);
 
-    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
@@ -2204,7 +2340,7 @@
     std::vector<LayerRequirement> layers = {
             {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
 
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
@@ -2225,7 +2361,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
         layers[0].focused = args.focused;
-        return selector.getBestFrameRateMode(layers, {.touch = args.touch})->getId();
+        return selector.getBestFrameRateMode(layers, {.touch = args.touch}).modePtr->getId();
     };
 
     constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
@@ -2234,7 +2370,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
 
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode()->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode().modePtr->getId());
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
     EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
@@ -2302,7 +2438,8 @@
     }
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+    EXPECT_EQ(kModeId90,
+              selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
     selector.setActiveMode(kModeId90, 90_Hz);
@@ -2319,7 +2456,7 @@
     }
 
     // Idle should be applied rather than the active mode when there are no layers.
-    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
@@ -2368,7 +2505,7 @@
 
     for (const auto& [fps, mode] : knownFrameRatesExpectations) {
         layer.desiredRefreshRate = fps;
-        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers));
+        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers).modePtr);
     }
 }
 
@@ -2471,17 +2608,17 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
     if (GetParam() == Config::FrameRateOverride::Disabled) {
-        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
     } else {
-        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}));
+        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
     }
 
     explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
@@ -2499,7 +2636,7 @@
     explicitDefaultLayer.name = "ExplicitDefault";
     explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
 }
 
 // b/190578904
@@ -2526,7 +2663,7 @@
         layers[0].desiredRefreshRate = fps;
         layers[0].vote = vote;
         EXPECT_EQ(fps.getIntValue(),
-                  selector.getBestFrameRateMode(layers)->getPeakFps().getIntValue())
+                  selector.getBestFrameRateMode(layers).modePtr->getPeakFps().getIntValue())
                 << "Failed for " << ftl::enum_string(vote);
     };
 
@@ -2565,7 +2702,7 @@
             },
     };
 
-    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getPeakFps());
+    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals).modePtr->getPeakFps());
 }
 
 TEST_P(RefreshRateSelectorTest, modeComparison) {
@@ -3176,7 +3313,8 @@
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+    EXPECT_EQ(kModeId90,
+              selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
     selector.setActiveMode(kModeId90, 90_Hz);
@@ -3192,7 +3330,7 @@
     }
 
     // Idle should be applied rather than the active mode when there are no layers.
-    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
 }
 
 TEST_P(RefreshRateSelectorTest, policyCanBeInfinity) {
diff --git a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
index b910485..05f9eed 100644
--- a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
@@ -23,38 +23,34 @@
 
 namespace android::scheduler {
 
-class SmallAreaDetectionMappingsAllowTest : public testing::Test {
+class SmallAreaDetectionAllowMappingsTest : public testing::Test {
 protected:
     SmallAreaDetectionAllowMappings mMappings;
+    static constexpr int32_t kAppId1 = 10100;
+    static constexpr int32_t kAppId2 = 10101;
+    static constexpr float kThreshold1 = 0.05f;
+    static constexpr float kThreshold2 = 0.07f;
 };
 
 namespace {
-TEST_F(SmallAreaDetectionMappingsAllowTest, testUpdate) {
-    const uid_t uid1 = 10100;
-    const uid_t uid2 = 10101;
-    const float threshold1 = 0.05f;
-    const float threshold2 = 0.07f;
-    std::vector<std::pair<uid_t, float>> mappings;
+TEST_F(SmallAreaDetectionAllowMappingsTest, testUpdate) {
+    std::vector<std::pair<int32_t, float>> mappings;
     mappings.reserve(2);
-    mappings.push_back(std::make_pair(uid1, threshold1));
-    mappings.push_back(std::make_pair(uid2, threshold2));
+    mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+    mappings.push_back(std::make_pair(kAppId2, kThreshold2));
 
     mMappings.update(mappings);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid1).value(), threshold1);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid2).value(), threshold2);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1).value(), kThreshold1);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId2).value(), kThreshold2);
 }
 
-TEST_F(SmallAreaDetectionMappingsAllowTest, testSetThesholdForUid) {
-    const uid_t uid = 10111;
-    const float threshold = 0.05f;
-
-    mMappings.setThesholdForUid(uid, threshold);
-    ASSERT_EQ(mMappings.getThresholdForUid(uid), threshold);
+TEST_F(SmallAreaDetectionAllowMappingsTest, testSetThesholdForAppId) {
+    mMappings.setThesholdForAppId(kAppId1, kThreshold1);
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), kThreshold1);
 }
 
-TEST_F(SmallAreaDetectionMappingsAllowTest, testUidNotInTheMappings) {
-    const uid_t uid = 10222;
-    ASSERT_EQ(mMappings.getThresholdForUid(uid), std::nullopt);
+TEST_F(SmallAreaDetectionAllowMappingsTest, testAppIdNotInTheMappings) {
+    ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), std::nullopt);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 014d07c..3d1c900 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -44,7 +44,10 @@
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
+          : Scheduler(*this, callback,
+                      (FeatureFlags)Feature::kContentDetection |
+                              Feature::kSmallDirtyContentDetection,
+                      std::move(modulatorPtr)) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index b54392e..8f1982d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -611,6 +611,15 @@
     void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
 
     auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+    auto setDaltonizerType(ColorBlindnessType type) {
+        mFlinger->mDaltonizer.setType(type);
+        return mFlinger->updateColorMatrixLocked();
+    }
+    auto updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+                              bool& out) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return mFlinger->updateLayerSnapshots(vsyncId, frameTimeNs, transactionsFlushed, out);
+    }
     /* ------------------------------------------------------------------------
      * Read-write access to private data to set up preconditions and assert
      * post-conditions.
@@ -661,6 +670,11 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
+    void enableNewFrontEnd() {
+        mFlinger->mLayerLifecycleManagerEnabled = true;
+        mFlinger->mLegacyFrontEndEnabled = false;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index caa265f..5046a86 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -347,7 +347,7 @@
         // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
         // pendingJankClassifications.
         EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
-        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                          /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         layer->releasePendingBuffer(25);
 
@@ -484,10 +484,10 @@
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
         for (auto& surfaceFrame : bufferlessSurfaceFrames) {
-            surfaceFrame->onPresent(20, JankType::None, 90_Hz,
+            surfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                     /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         }
-        presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+        presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                                /*displayDeadlineDelta*/ 0,
                                                /*displayPresentDelta*/ 0);
 
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index a1eda94..1dc5498 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -759,6 +759,8 @@
 
 // b/1450138150
 TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
@@ -772,6 +774,29 @@
                                  {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
+
+    advanceToNextCallback();
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
+// b/1450138150
+TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
+    mMockClock.advanceBy(400);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(400, *result);
+
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
@@ -826,6 +851,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     EXPECT_CALL(mMockClock, alarmAt(_, 600));
 
     CountingCallback cb(mDispatch);
@@ -843,6 +870,26 @@
     advanceToNextCallback();
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    EXPECT_CALL(mMockClock, alarmAt(_, 600));
+
+    CountingCallback cb(mDispatch);
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
+
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(0, *result);
+
+    advanceToNextCallback();
+}
+
 TEST_F(VSyncDispatchTimerQueueTest, helperMove) {
     EXPECT_CALL(mMockClock, alarmAt(_, 500)).Times(1);
     EXPECT_CALL(mMockClock, alarmCancel()).Times(1);
@@ -1045,6 +1092,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
 
@@ -1065,10 +1114,31 @@
     EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
 }
 
-// TODO(b/304338314): Set the flag value instead of skipping the test
+TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) {
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+
+    CountingCallback cb(mDispatch);
+
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+
+    advanceToNextCallback();
+
+    advanceToNextCallback();
+
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+    EXPECT_THAT(cb.mCalls[0], Eq(1000));
+    ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+    EXPECT_THAT(cb.mWakeupTime[0], Eq(600));
+    ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+    EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
+}
+
 TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
-    // SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
-    if (flags::dont_skip_on_early()) GTEST_SKIP();
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
 
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
@@ -1088,10 +1158,8 @@
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
 
-// TODO(b/304338314): Set the flag value instead of skipping the test
 TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
-    // SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
-    if (!flags::dont_skip_on_early()) GTEST_SKIP();
+    SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
 
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
@@ -1105,7 +1173,7 @@
     result = mDispatch->schedule(cb,
                                  {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
-    EXPECT_EQ(200, *result);
+    EXPECT_EQ(300, *result);
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
index 5dc48c3..3f2a917 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -31,7 +31,7 @@
 
     MOCK_METHOD0(onBootFinished, void());
     MOCK_METHOD1(addSurfaceFrame, void(std::shared_ptr<frametimeline::SurfaceFrame>));
-    MOCK_METHOD3(setSfWakeUp, void(int64_t, nsecs_t, Fps));
+    MOCK_METHOD4(setSfWakeUp, void(int64_t, nsecs_t, Fps, Fps));
     MOCK_METHOD3(setSfPresent,
                  void(nsecs_t, const std::shared_ptr<FenceTime>&,
                       const std::shared_ptr<FenceTime>&));