Merge "TEST_MAPPING: re-enable fixed scheduler test" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 17e5cc1..dd0ee5f 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1526,7 +1526,7 @@
 }
 
 static void DumpstateLimitedOnly() {
-    // Trimmed-down version of dumpstate to only include a whitelisted
+    // Trimmed-down version of dumpstate to only include a allowlisted
     // set of logs (system log, event log, and system server / system app
     // crashes, and networking logs). See b/136273873 and b/138459828
     // for context.
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 9d2c791..ba8b02d 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -60,27 +60,6 @@
 
 struct APerformanceHintManager;
 struct APerformanceHintSession;
-struct AWorkDuration;
-
-/**
- * {@link AWorkDuration} is an opaque type that represents the breakdown of the
- * actual workload duration in each component internally.
- *
- * A new {@link AWorkDuration} can be obtained using
- * {@link AWorkDuration_create()}, when the client finishes using
- * {@link AWorkDuration}, {@link AWorkDuration_release()} must be
- * called to destroy and free up the resources associated with
- * {@link AWorkDuration}.
- *
- * This file provides a set of functions to allow clients to set the measured
- * work duration of each component on {@link AWorkDuration}.
- *
- * - AWorkDuration_setWorkPeriodStartTimestampNanos()
- * - AWorkDuration_setActualTotalDurationNanos()
- * - AWorkDuration_setActualCpuDurationNanos()
- * - AWorkDuration_setActualGpuDurationNanos()
- */
-typedef struct AWorkDuration AWorkDuration;
 
 /**
  * An opaque type representing a handle to a performance hint manager.
@@ -123,7 +102,7 @@
   *
   * @return APerformanceHintManager instance on success, nullptr on failure.
   */
-APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
@@ -137,9 +116,9 @@
  *     This must be positive if using the work duration API, or 0 otherwise.
  * @return APerformanceHintManager instance on success, nullptr on failure.
  */
-APerformanceHintSession* _Nullable APerformanceHint_createSession(
-        APerformanceHintManager* _Nonnull manager,
-        const int32_t* _Nonnull threadIds, size_t size,
+APerformanceHintSession* APerformanceHint_createSession(
+        APerformanceHintManager* manager,
+        const int32_t* threadIds, size_t size,
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -149,7 +128,7 @@
  * @return the preferred update rate supported by device software.
  */
 int64_t APerformanceHint_getPreferredUpdateRateNanos(
-        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Updates this session's target duration for each cycle of work.
@@ -161,7 +140,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_updateTargetWorkDuration(
-        APerformanceHintSession* _Nonnull session,
+        APerformanceHintSession* session,
         int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -178,7 +157,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration(
-        APerformanceHintSession* _Nonnull session,
+        APerformanceHintSession* session,
         int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -188,7 +167,7 @@
  * @param session The performance hint session instance to release.
  */
 void APerformanceHint_closeSession(
-        APerformanceHintSession* _Nonnull session) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Set a list of threads to the performance hint session. This operation will replace
@@ -205,8 +184,8 @@
  *         EPERM if any thread id doesn't belong to the application.
  */
 int APerformanceHint_setThreads(
-        APerformanceHintSession* _Nonnull session,
-        const pid_t* _Nonnull threadIds,
+        APerformanceHintSession* session,
+        const pid_t* threadIds,
         size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
@@ -219,92 +198,11 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_setPreferPowerEfficiency(
-        APerformanceHintSession* _Nonnull session,
+        APerformanceHintSession* session,
         bool enabled) __INTRODUCED_IN(__ANDROID_API_V__);
 
-/**
- * Reports the durations for the last cycle of work.
- *
- * The system will attempt to adjust the scheduling and performance of the
- * threads within the thread group to bring the actual duration close to the target duration.
- *
- * @param session The {@link APerformanceHintSession} instance to update.
- * @param workDuration The {@link AWorkDuration} structure of times the thread group took to
- *     complete its last task in nanoseconds breaking down into different components.
- *
- *     The work period start timestamp, actual total duration and actual CPU duration must be
- *     positive.
- *
- *     The actual GPU duration must be non-negative. If the actual GPU duration is 0, it means
- *     the actual GPU duration is not measured.
- *
- * @return 0 on success.
- *         EINVAL if session is nullptr or any duration is an invalid number.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_reportActualWorkDuration2(
-        APerformanceHintSession* _Nonnull session,
-        AWorkDuration* _Nonnull workDuration) __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
- * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
- * associated with it.
- *
- * @return AWorkDuration on success and nullptr otherwise.
- */
-AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Destroys {@link AWorkDuration} and free all resources associated to it.
- *
- * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- */
-void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Sets the work period start timestamp in nanoseconds.
- *
- * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on
- *        CLOCK_MONOTONIC about when the work starts, the timestamp must be positive.
- */
-void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration,
-        int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Sets the actual total work duration in nanoseconds.
- *
- * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be
- *        positive.
- */
-void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
-        int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Sets the actual CPU work duration in nanoseconds.
- *
- * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be
- *        positive.
- */
-void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
-        int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
-
-/**
- * Sets the actual GPU work duration in nanoseconds.
- *
- * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}.
- * @param actualGpuDurationNanos The actual GPU work duration in nanoseconds, the number must be
- *        non-negative. If the actual GPU duration is 0, it means the actual GPU duration is
- *        measured.
- */
-void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
-        int64_t actualGpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
-
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
 
-/** @} */
+/** @} */
\ No newline at end of file
diff --git a/include/binder/unique_fd.h b/include/binder/unique_fd.h
new file mode 120000
index 0000000..433c968
--- /dev/null
+++ b/include/binder/unique_fd.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/unique_fd.h
\ No newline at end of file
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 4f53c36..59b9495 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -240,7 +240,7 @@
                                                 android::base::unique_fd fd, sp<IBinder> token);
     InputChannel() = default;
     InputChannel(const InputChannel& other)
-          : mName(other.mName), mFd(::dup(other.mFd)), mToken(other.mToken){};
+          : mName(other.mName), mFd(other.dupFd()), mToken(other.mToken){};
     InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
     ~InputChannel() override;
     /**
@@ -310,7 +310,7 @@
         if (fstat(mFd.get(), &lhs) != 0) {
             return false;
         }
-        if (fstat(inputChannel.getFd(), &rhs) != 0) {
+        if (fstat(inputChannel.getFd().get(), &rhs) != 0) {
             return false;
         }
         // If file descriptors are pointing to same inode they are duplicated fds.
@@ -322,7 +322,7 @@
     base::unique_fd dupFd() const;
 
     std::string mName;
-    android::base::unique_fd mFd;
+    base::unique_fd mFd;
 
     sp<IBinder> mToken;
 };
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index f90c618..eccd5db 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -92,6 +92,7 @@
         "TextOutput.cpp",
         "Trace.cpp",
         "Utils.cpp",
+        "file.cpp",
     ],
 
     shared_libs: [
@@ -653,4 +654,7 @@
         "libutils",
         "android.debug_aidl-cpp",
     ],
+    static_libs: [
+        "libc++fs",
+    ],
 }
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 301dbf6..c57c9cd 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -19,7 +19,6 @@
 #include <atomic>
 #include <set>
 
-#include <android-base/unique_fd.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
 #include <binder/IPCThreadState.h>
@@ -28,6 +27,7 @@
 #include <binder/Parcel.h>
 #include <binder/RecordedTransaction.h>
 #include <binder/RpcServer.h>
+#include <binder/unique_fd.h>
 #include <pthread.h>
 
 #include <inttypes.h>
@@ -43,6 +43,8 @@
 
 namespace android {
 
+using android::binder::unique_fd;
+
 constexpr uid_t kUidRoot = 0;
 
 // Service implementations inherit from BBinder and IBinder, and this is frozen
@@ -167,8 +169,7 @@
     return OK;
 }
 
-status_t IBinder::setRpcClientDebug(android::base::unique_fd socketFd,
-                                    const sp<IBinder>& keepAliveBinder) {
+status_t IBinder::setRpcClientDebug(unique_fd socketFd, const sp<IBinder>& keepAliveBinder) {
     if (!kEnableRpcDevServers) {
         ALOGW("setRpcClientDebug disallowed because RPC is not enabled");
         return INVALID_OPERATION;
@@ -273,7 +274,7 @@
     std::set<sp<RpcServerLink>> mRpcServerLinks;
     BpBinder::ObjectManager mObjects;
 
-    android::base::unique_fd mRecordingFd;
+    unique_fd mRecordingFd;
 };
 
 // ---------------------------------------------------------------------------
@@ -640,7 +641,7 @@
     }
     status_t status;
     bool hasSocketFd;
-    android::base::unique_fd clientFd;
+    unique_fd clientFd;
 
     if (status = data.readBool(&hasSocketFd); status != OK) return status;
     if (hasSocketFd) {
@@ -652,8 +653,7 @@
     return setRpcClientDebug(std::move(clientFd), keepAliveBinder);
 }
 
-status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd,
-                                    const sp<IBinder>& keepAliveBinder) {
+status_t BBinder::setRpcClientDebug(unique_fd socketFd, const sp<IBinder>& keepAliveBinder) {
     if (!kEnableRpcDevServers) {
         ALOGW("%s: disallowed because RPC is not enabled", __PRETTY_FUNCTION__);
         return INVALID_OPERATION;
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 49038b1..42dd691 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -27,14 +27,15 @@
 #include <stdio.h>
 
 #include "BuildFlags.h"
-
-#include <android-base/file.h>
+#include "file.h"
 
 //#undef ALOGV
 //#define ALOGV(...) fprintf(stderr, __VA_ARGS__)
 
 namespace android {
 
+using android::binder::unique_fd;
+
 // ---------------------------------------------------------------------------
 
 RpcMutex BpBinder::sTrackingLock;
@@ -318,7 +319,7 @@
     return transact(PING_TRANSACTION, data, &reply);
 }
 
-status_t BpBinder::startRecordingBinder(const android::base::unique_fd& fd) {
+status_t BpBinder::startRecordingBinder(const unique_fd& fd) {
     Parcel send, reply;
     send.writeUniqueFileDescriptor(fd);
     return transact(START_RECORDING_TRANSACTION, send, &reply);
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index 895690f..455a433 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -23,6 +23,7 @@
 
 #include <binder/Functional.h>
 
+#include "FdUtils.h"
 #include "RpcState.h"
 #include "Utils.h"
 
@@ -33,7 +34,7 @@
 std::unique_ptr<FdTrigger> FdTrigger::make() {
     auto ret = std::make_unique<FdTrigger>();
 #ifndef BINDER_RPC_SINGLE_THREADED
-    if (!android::base::Pipe(&ret->mRead, &ret->mWrite)) {
+    if (!binder::Pipe(&ret->mRead, &ret->mWrite)) {
         ALOGE("Could not create pipe %s", strerror(errno));
         return nullptr;
     }
diff --git a/libs/binder/FdTrigger.h b/libs/binder/FdTrigger.h
index dba1dc9..e4a0283 100644
--- a/libs/binder/FdTrigger.h
+++ b/libs/binder/FdTrigger.h
@@ -17,10 +17,10 @@
 
 #include <memory>
 
-#include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 
 namespace android {
 
@@ -61,8 +61,8 @@
 #ifdef BINDER_RPC_SINGLE_THREADED
     bool mTriggered = false;
 #else
-    base::unique_fd mWrite;
-    base::unique_fd mRead;
+    binder::unique_fd mWrite;
+    binder::unique_fd mRead;
 #endif
 };
 } // namespace android
diff --git a/libs/binder/FdUtils.h b/libs/binder/FdUtils.h
new file mode 100644
index 0000000..52ae487
--- /dev/null
+++ b/libs/binder/FdUtils.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <binder/unique_fd.h>
+
+#if defined(_WIN32) || defined(__TRUSTY__)
+// Pipe and Socketpair are missing there
+#elif !defined(BINDER_NO_LIBBASE)
+
+namespace android::binder {
+using android::base::Pipe;
+using android::base::Socketpair;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <sys/socket.h>
+
+namespace android::binder {
+
+// Inline functions, so that they can be used header-only.
+
+// See pipe(2).
+// This helper hides the details of converting to unique_fd, and also hides the
+// fact that macOS doesn't support O_CLOEXEC or O_NONBLOCK directly.
+inline bool Pipe(unique_fd* read, unique_fd* write, int flags = O_CLOEXEC) {
+    int pipefd[2];
+
+#if defined(__APPLE__)
+    if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
+        return false;
+    }
+    if (pipe(pipefd) != 0) {
+        return false;
+    }
+
+    if (flags & O_CLOEXEC) {
+        if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 ||
+            fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
+            close(pipefd[0]);
+            close(pipefd[1]);
+            return false;
+        }
+    }
+    if (flags & O_NONBLOCK) {
+        if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 ||
+            fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
+            close(pipefd[0]);
+            close(pipefd[1]);
+            return false;
+        }
+    }
+#else
+    if (pipe2(pipefd, flags) != 0) {
+        return false;
+    }
+#endif
+
+    read->reset(pipefd[0]);
+    write->reset(pipefd[1]);
+    return true;
+}
+
+// See socketpair(2).
+// This helper hides the details of converting to unique_fd.
+inline bool Socketpair(int domain, int type, int protocol, unique_fd* left, unique_fd* right) {
+    int sockfd[2];
+    if (socketpair(domain, type, protocol, sockfd) != 0) {
+        return false;
+    }
+    left->reset(sockfd[0]);
+    right->reset(sockfd[1]);
+    return true;
+}
+
+// See socketpair(2).
+// This helper hides the details of converting to unique_fd.
+inline bool Socketpair(int type, unique_fd* left, unique_fd* right) {
+    return Socketpair(AF_UNIX, type, 0, left, right);
+}
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index bb7caa9..c5f0730 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -18,13 +18,13 @@
 #include <stddef.h>
 #include <cstdint>
 
-#include <android-base/unique_fd.h>
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 
 namespace android::binder::os {
 
-status_t setNonBlocking(android::base::borrowed_fd fd);
+status_t setNonBlocking(borrowed_fd fd);
 
 status_t getRandomBytes(uint8_t* data, size_t size);
 
@@ -32,13 +32,11 @@
 
 std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory();
 
-ssize_t sendMessageOnSocket(
-        const RpcTransportFd& socket, iovec* iovs, int niovs,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
+ssize_t sendMessageOnSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+                            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds);
 
-ssize_t receiveMessageFromSocket(
-        const RpcTransportFd& socket, iovec* iovs, int niovs,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
+ssize_t receiveMessageFromSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+                                 std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds);
 
 uint64_t GetThreadId();
 
diff --git a/libs/binder/OS_unix_base.cpp b/libs/binder/OS_unix_base.cpp
index a3cf117..ca998d4 100644
--- a/libs/binder/OS_unix_base.cpp
+++ b/libs/binder/OS_unix_base.cpp
@@ -16,18 +16,21 @@
 
 #include "OS.h"
 #include "Utils.h"
+#include "file.h"
 
-#include <android-base/file.h>
 #include <binder/RpcTransportRaw.h>
 #include <log/log.h>
 #include <string.h>
+#include <sys/socket.h>
+
+using android::binder::ReadFully;
 
 namespace android::binder::os {
 
 // Linux kernel supports up to 253 (from SCM_MAX_FD) for unix sockets.
 constexpr size_t kMaxFdsPerMsg = 253;
 
-status_t setNonBlocking(android::base::borrowed_fd fd) {
+status_t setNonBlocking(borrowed_fd fd) {
     int flags = TEMP_FAILURE_RETRY(fcntl(fd.get(), F_GETFL));
     if (flags == -1) {
         PLOGE("Failed setNonBlocking: Could not get flags for fd");
@@ -41,13 +44,12 @@
 }
 
 status_t getRandomBytes(uint8_t* data, size_t size) {
-    int ret = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
-    if (ret == -1) {
+    unique_fd fd(TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
+    if (!fd.ok()) {
         return -errno;
     }
 
-    base::unique_fd fd(ret);
-    if (!base::ReadFully(fd, data, size)) {
+    if (!ReadFully(fd, data, size)) {
         return -errno;
     }
     return OK;
@@ -67,9 +69,8 @@
     return RpcTransportCtxFactoryRaw::make();
 }
 
-ssize_t sendMessageOnSocket(
-        const RpcTransportFd& socket, iovec* iovs, int niovs,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ssize_t sendMessageOnSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+                            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr && !ancillaryFds->empty()) {
         if (ancillaryFds->size() > kMaxFdsPerMsg) {
             errno = EINVAL;
@@ -112,9 +113,8 @@
     return TEMP_FAILURE_RETRY(sendmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
 }
 
-ssize_t receiveMessageFromSocket(
-        const RpcTransportFd& socket, iovec* iovs, int niovs,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ssize_t receiveMessageFromSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+                                 std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr) {
         int fdBuffer[kMaxFdsPerMsg];
         alignas(struct cmsghdr) char msgControlBuf[CMSG_SPACE(sizeof(fdBuffer))];
@@ -140,7 +140,7 @@
                 size_t fdCount = dataLen / sizeof(int);
                 ancillaryFds->reserve(ancillaryFds->size() + fdCount);
                 for (size_t i = 0; i < fdCount; i++) {
-                    ancillaryFds->emplace_back(base::unique_fd(fdBuffer[i]));
+                    ancillaryFds->emplace_back(unique_fd(fdBuffer[i]));
                 }
                 break;
             }
diff --git a/libs/binder/OWNERS b/libs/binder/OWNERS
index bb17683..a70b558 100644
--- a/libs/binder/OWNERS
+++ b/libs/binder/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 32456
-maco@google.com
+
 smoreland@google.com
-tkjos@google.com
+tkjos@google.com # kernel
+maco@google.com # historical
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 3349402..146ddef 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -97,6 +97,8 @@
 namespace android {
 
 using namespace android::binder::impl;
+using binder::borrowed_fd;
+using binder::unique_fd;
 
 // many things compile this into prebuilts on the stack
 #ifdef __LP64__
@@ -211,7 +213,7 @@
 }
 #endif // BINDER_WITH_KERNEL_IPC
 
-static int toRawFd(const std::variant<base::unique_fd, base::borrowed_fd>& v) {
+static int toRawFd(const std::variant<unique_fd, borrowed_fd>& v) {
     return std::visit([](const auto& fd) { return fd.get(); }, v);
 }
 
@@ -667,7 +669,7 @@
                 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));
+                rpcFields->mFds->emplace_back(unique_fd(newFd));
                 // Fixup the index in the data.
                 mDataPos = newDataPos + 4;
                 if (status_t status = writeInt32(rpcFields->mFds->size() - 1); status != OK) {
@@ -1247,9 +1249,16 @@
                         const std::unique_ptr<std::vector<std::unique_ptr<std::string>>>& val) { return writeData(val); }
 status_t Parcel::writeUtf8VectorAsUtf16Vector(const std::vector<std::string>& val) { return writeData(val); }
 
-status_t Parcel::writeUniqueFileDescriptorVector(const std::vector<base::unique_fd>& val) { return writeData(val); }
-status_t Parcel::writeUniqueFileDescriptorVector(const std::optional<std::vector<base::unique_fd>>& val) { return writeData(val); }
-status_t Parcel::writeUniqueFileDescriptorVector(const std::unique_ptr<std::vector<base::unique_fd>>& val) { return writeData(val); }
+status_t Parcel::writeUniqueFileDescriptorVector(const std::vector<unique_fd>& val) {
+    return writeData(val);
+}
+status_t Parcel::writeUniqueFileDescriptorVector(const std::optional<std::vector<unique_fd>>& val) {
+    return writeData(val);
+}
+status_t Parcel::writeUniqueFileDescriptorVector(
+        const std::unique_ptr<std::vector<unique_fd>>& val) {
+    return writeData(val);
+}
 
 status_t Parcel::writeStrongBinderVector(const std::vector<sp<IBinder>>& val) { return writeData(val); }
 status_t Parcel::writeStrongBinderVector(const std::optional<std::vector<sp<IBinder>>>& val) { return writeData(val); }
@@ -1302,9 +1311,16 @@
         std::unique_ptr<std::vector<std::unique_ptr<std::string>>>* val) const { return readData(val); }
 status_t Parcel::readUtf8VectorFromUtf16Vector(std::vector<std::string>* val) const { return readData(val); }
 
-status_t Parcel::readUniqueFileDescriptorVector(std::optional<std::vector<base::unique_fd>>* val) const { return readData(val); }
-status_t Parcel::readUniqueFileDescriptorVector(std::unique_ptr<std::vector<base::unique_fd>>* val) const { return readData(val); }
-status_t Parcel::readUniqueFileDescriptorVector(std::vector<base::unique_fd>* val) const { return readData(val); }
+status_t Parcel::readUniqueFileDescriptorVector(std::optional<std::vector<unique_fd>>* val) const {
+    return readData(val);
+}
+status_t Parcel::readUniqueFileDescriptorVector(
+        std::unique_ptr<std::vector<unique_fd>>* val) const {
+    return readData(val);
+}
+status_t Parcel::readUniqueFileDescriptorVector(std::vector<unique_fd>* val) const {
+    return readData(val);
+}
 
 status_t Parcel::readStrongBinderVector(std::optional<std::vector<sp<IBinder>>>* val) const { return readData(val); }
 status_t Parcel::readStrongBinderVector(std::unique_ptr<std::vector<sp<IBinder>>>* val) const { return readData(val); }
@@ -1504,11 +1520,11 @@
 
 status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
     if (auto* rpcFields = maybeRpcFields()) {
-        std::variant<base::unique_fd, base::borrowed_fd> fdVariant;
+        std::variant<unique_fd, borrowed_fd> fdVariant;
         if (takeOwnership) {
-            fdVariant = base::unique_fd(fd);
+            fdVariant = unique_fd(fd);
         } else {
-            fdVariant = base::borrowed_fd(fd);
+            fdVariant = borrowed_fd(fd);
         }
         if (!mAllowFds) {
             return FDS_NOT_ALLOWED;
@@ -1587,7 +1603,7 @@
     return err;
 }
 
-status_t Parcel::writeUniqueFileDescriptor(const base::unique_fd& fd) {
+status_t Parcel::writeUniqueFileDescriptor(const unique_fd& fd) {
     return writeDupFileDescriptor(fd.get());
 }
 
@@ -2390,8 +2406,7 @@
     return fd;
 }
 
-status_t Parcel::readUniqueFileDescriptor(base::unique_fd* val) const
-{
+status_t Parcel::readUniqueFileDescriptor(unique_fd* val) const {
     int got = readFileDescriptor();
 
     if (got == BAD_TYPE) {
@@ -2412,8 +2427,7 @@
     return OK;
 }
 
-status_t Parcel::readUniqueParcelFileDescriptor(base::unique_fd* val) const
-{
+status_t Parcel::readUniqueParcelFileDescriptor(unique_fd* val) const {
     int got = readParcelFileDescriptor();
 
     if (got == BAD_TYPE) {
@@ -2710,8 +2724,7 @@
 status_t Parcel::rpcSetDataReference(
         const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
         const uint32_t* objectTable, size_t objectTableSize,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds,
-        release_func relFunc) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds, release_func relFunc) {
     // this code uses 'mOwner == nullptr' to understand whether it owns memory
     LOG_ALWAYS_FATAL_IF(relFunc == nullptr, "must provide cleanup function");
 
diff --git a/libs/binder/ParcelFileDescriptor.cpp b/libs/binder/ParcelFileDescriptor.cpp
index 4f8b76f..c3c4874 100644
--- a/libs/binder/ParcelFileDescriptor.cpp
+++ b/libs/binder/ParcelFileDescriptor.cpp
@@ -19,9 +19,11 @@
 namespace android {
 namespace os {
 
+using android::binder::unique_fd;
+
 ParcelFileDescriptor::ParcelFileDescriptor() = default;
 
-ParcelFileDescriptor::ParcelFileDescriptor(android::base::unique_fd fd) : mFd(std::move(fd)) {}
+ParcelFileDescriptor::ParcelFileDescriptor(unique_fd fd) : mFd(std::move(fd)) {}
 
 ParcelFileDescriptor::~ParcelFileDescriptor() = default;
 
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 0344eb0..7de94e3 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -61,6 +61,7 @@
 namespace android {
 
 using namespace android::binder::impl;
+using android::binder::unique_fd;
 
 class PoolThread : public Thread
 {
@@ -514,8 +515,8 @@
     return mDriverName;
 }
 
-static base::unique_fd open_driver(const char* driver) {
-    auto fd = base::unique_fd(open(driver, O_RDWR | O_CLOEXEC));
+static unique_fd open_driver(const char* driver) {
+    auto fd = unique_fd(open(driver, O_RDWR | O_CLOEXEC));
     if (!fd.ok()) {
         PLOGE("Opening '%s' failed", driver);
         return {};
@@ -563,7 +564,7 @@
         mThreadPoolStarted(false),
         mThreadPoolSeq(1),
         mCallRestriction(CallRestriction::NONE) {
-    base::unique_fd opened = open_driver(driver);
+    unique_fd opened = open_driver(driver);
 
     if (opened.ok()) {
         // mmap the binder, providing a chunk of virtual address space to receive transactions.
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index f7b5a55..525ba2e 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -14,18 +14,23 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
-#include <android-base/unique_fd.h>
+#include "file.h"
+
 #include <binder/Functional.h>
 #include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
+
 #include <inttypes.h>
 #include <sys/mman.h>
+#include <sys/stat.h>
 #include <algorithm>
 
 using namespace android::binder::impl;
 using android::Parcel;
-using android::base::borrowed_fd;
-using android::base::unique_fd;
+using android::binder::borrowed_fd;
+using android::binder::ReadFully;
+using android::binder::unique_fd;
+using android::binder::WriteFully;
 using android::binder::debug::RecordedTransaction;
 
 #define PADDING8(s) ((8 - (s) % 8) % 8)
@@ -182,7 +187,7 @@
             return std::nullopt;
         }
 
-        if (!android::base::ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
+        if (!ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
             ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno));
             return std::nullopt;
         }
@@ -317,7 +322,7 @@
     buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
 
     // Write buffer to file
-    if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
+    if (!WriteFully(fd, buffer.data(), buffer.size())) {
         ALOGE("Failed to write chunk fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index fefaa81..d9e926a 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -45,7 +45,8 @@
 constexpr size_t kSessionIdBytes = 32;
 
 using namespace android::binder::impl;
-using base::unique_fd;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 RpcServer::RpcServer(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {}
 RpcServer::~RpcServer() {
@@ -166,7 +167,7 @@
     mConnectionFilter = std::move(filter);
 }
 
-void RpcServer::setServerSocketModifier(std::function<void(base::borrowed_fd)>&& modifier) {
+void RpcServer::setServerSocketModifier(std::function<void(borrowed_fd)>&& modifier) {
     RpcMutexLockGuard _l(mLock);
     LOG_ALWAYS_FATAL_IF(mServer.fd.ok(), "Already started");
     mServerSocketModifier = std::move(modifier);
@@ -213,7 +214,7 @@
 status_t RpcServer::recvmsgSocketConnection(const RpcServer& server, RpcTransportFd* out) {
     int zero = 0;
     iovec iov{&zero, sizeof(zero)};
-    std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+    std::vector<std::variant<unique_fd, borrowed_fd>> fds;
 
     ssize_t num_bytes = binder::os::receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
     if (num_bytes < 0) {
@@ -644,8 +645,7 @@
 }
 
 status_t RpcServer::setupExternalServer(
-        base::unique_fd serverFd,
-        std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn) {
+        unique_fd serverFd, std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn) {
     RpcMutexLockGuard _l(mLock);
     if (mServer.fd.ok()) {
         ALOGE("Each RpcServer can only have one server.");
@@ -656,7 +656,7 @@
     return OK;
 }
 
-status_t RpcServer::setupExternalServer(base::unique_fd serverFd) {
+status_t RpcServer::setupExternalServer(unique_fd serverFd) {
     return setupExternalServer(std::move(serverFd), &RpcServer::acceptSocketConnection);
 }
 
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index cd8f417..16a7f9f 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -51,7 +51,8 @@
 namespace android {
 
 using namespace android::binder::impl;
-using base::unique_fd;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 RpcSession::RpcSession(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {
     LOG_RPC_DETAIL("RpcSession created %p", this);
@@ -157,7 +158,7 @@
 
         int zero = 0;
         iovec iov{&zero, sizeof(zero)};
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+        std::vector<std::variant<unique_fd, borrowed_fd>> fds;
         fds.push_back(std::move(serverFd));
 
         status_t status = mBootstrapTransport->interruptableWriteFully(mShutdownTrigger.get(), &iov,
@@ -186,8 +187,7 @@
     return NAME_NOT_FOUND;
 }
 
-status_t RpcSession::setupPreconnectedClient(base::unique_fd fd,
-                                             std::function<unique_fd()>&& request) {
+status_t RpcSession::setupPreconnectedClient(unique_fd fd, std::function<unique_fd()>&& request) {
     return setupClient([&](const std::vector<uint8_t>& sessionId, bool incoming) -> status_t {
         if (!fd.ok()) {
             fd = request();
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 008e5d2..fe6e1a3 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -39,6 +39,8 @@
 namespace android {
 
 using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 #if RPC_FLAKE_PRONE
 void rpcMaybeWaitToFlake() {
@@ -355,11 +357,10 @@
     mData.reset(new (std::nothrow) uint8_t[size]);
 }
 
-status_t RpcState::rpcSend(
-        const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
-        const char* what, iovec* iovs, int niovs,
-        const std::optional<SmallFunction<status_t()>>& altPoll,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection,
+                           const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
+                           const std::optional<SmallFunction<status_t()>>& altPoll,
+                           const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     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(),
@@ -380,10 +381,9 @@
     return OK;
 }
 
-status_t RpcState::rpcRec(
-        const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
-        const char* what, iovec* iovs, int niovs,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+status_t RpcState::rpcRec(const sp<RpcSession::RpcConnection>& connection,
+                          const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
+                          std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     if (status_t status =
                 connection->rpcTransport->interruptableReadFully(session->mShutdownTrigger.get(),
                                                                  iovs, niovs, std::nullopt,
@@ -649,7 +649,7 @@
 
 status_t RpcState::waitForReply(const sp<RpcSession::RpcConnection>& connection,
                                 const sp<RpcSession>& session, Parcel* reply) {
-    std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+    std::vector<std::variant<unique_fd, borrowed_fd>> ancillaryFds;
     RpcWireHeader command;
     while (true) {
         iovec iov{&command, sizeof(command)};
@@ -767,7 +767,7 @@
                                         const sp<RpcSession>& session, CommandType type) {
     LOG_RPC_DETAIL("getAndExecuteCommand on RpcTransport %p", connection->rpcTransport.get());
 
-    std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+    std::vector<std::variant<unique_fd, borrowed_fd>> ancillaryFds;
     RpcWireHeader command;
     iovec iov{&command, sizeof(command)};
     if (status_t status =
@@ -796,7 +796,7 @@
 status_t RpcState::processCommand(
         const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
         const RpcWireHeader& command, CommandType type,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
 #ifdef BINDER_WITH_KERNEL_IPC
     IPCThreadState* kernelBinderState = IPCThreadState::selfOrNull();
     IPCThreadState::SpGuard spGuard{
@@ -836,7 +836,7 @@
 status_t RpcState::processTransact(
         const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
         const RpcWireHeader& command,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
     LOG_ALWAYS_FATAL_IF(command.command != RPC_COMMAND_TRANSACT, "command: %d", command.command);
 
     CommandData transactionData(command.bodySize);
@@ -863,7 +863,7 @@
 status_t RpcState::processTransactInternal(
         const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
         CommandData transactionData,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
     // for 'recursive' calls to this, we have already read and processed the
     // binder from the transaction data and taken reference counts into account,
     // so it is cached here.
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index 2a954e6..8b84602 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -15,12 +15,12 @@
  */
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/Functional.h>
 #include <binder/IBinder.h>
 #include <binder/Parcel.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
 
 #include <map>
 #include <optional>
@@ -192,27 +192,28 @@
             const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
             const char* what, iovec* iovs, int niovs,
             const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds =
+            const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds =
                     nullptr);
-    [[nodiscard]] status_t rpcRec(
-            const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
-            const char* what, iovec* iovs, int niovs,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds = nullptr);
+    [[nodiscard]] status_t rpcRec(const sp<RpcSession::RpcConnection>& connection,
+                                  const sp<RpcSession>& session, const char* what, iovec* iovs,
+                                  int niovs,
+                                  std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
+                                          ancillaryFds = nullptr);
 
     [[nodiscard]] status_t waitForReply(const sp<RpcSession::RpcConnection>& connection,
                                         const sp<RpcSession>& session, Parcel* reply);
     [[nodiscard]] status_t processCommand(
             const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
             const RpcWireHeader& command, CommandType type,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
     [[nodiscard]] status_t processTransact(
             const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
             const RpcWireHeader& command,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
     [[nodiscard]] status_t processTransactInternal(
             const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
             CommandData transactionData,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
     [[nodiscard]] status_t processDecStrong(const sp<RpcSession::RpcConnection>& connection,
                                             const sp<RpcSession>& session,
                                             const RpcWireHeader& command);
@@ -254,7 +255,7 @@
         struct AsyncTodo {
             sp<IBinder> ref;
             CommandData data;
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>> ancillaryFds;
             uint64_t asyncNumber = 0;
 
             bool operator<(const AsyncTodo& o) const {
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index ffa3151..aa3a6e5 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -19,6 +19,7 @@
 
 #include <poll.h>
 #include <stddef.h>
+#include <sys/socket.h>
 
 #include <binder/RpcTransportRaw.h>
 
@@ -30,6 +31,8 @@
 namespace android {
 
 using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 // RpcTransport with TLS disabled.
 class RpcTransportRaw : public RpcTransport {
@@ -57,8 +60,7 @@
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         bool sentFds = false;
         auto send = [&](iovec* iovs, int niovs) -> ssize_t {
             ssize_t ret = binder::os::sendMessageOnSocket(mSocket, iovs, niovs,
@@ -73,7 +75,7 @@
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+            std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         auto recv = [&](iovec* iovs, int niovs) -> ssize_t {
             return binder::os::receiveMessageFromSocket(mSocket, iovs, niovs, ancillaryFds);
         };
diff --git a/libs/binder/RpcTransportTipcAndroid.cpp b/libs/binder/RpcTransportTipcAndroid.cpp
index 188ba3b..3819fb6 100644
--- a/libs/binder/RpcTransportTipcAndroid.cpp
+++ b/libs/binder/RpcTransportTipcAndroid.cpp
@@ -27,6 +27,8 @@
 #include "RpcTransportUtils.h"
 
 using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 namespace android {
 
@@ -75,8 +77,7 @@
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         auto writeFn = [&](iovec* iovs, size_t niovs) -> ssize_t {
             // TODO: send ancillaryFds. For now, we just abort if anyone tries
             // to send any.
@@ -93,8 +94,7 @@
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /*ancillaryFds*/)
-            override {
+            std::vector<std::variant<unique_fd, borrowed_fd>>* /*ancillaryFds*/) override {
         auto readFn = [&](iovec* iovs, size_t niovs) -> ssize_t {
             // Fill the read buffer at most once per readFn call, then try to
             // return as much of it as possible. If the input iovecs are spread
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index fef4be4..579694c 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -18,6 +18,7 @@
 #include <log/log.h>
 
 #include <poll.h>
+#include <sys/socket.h>
 
 #include <openssl/bn.h>
 #include <openssl/ssl.h>
@@ -42,6 +43,8 @@
 namespace android {
 
 using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 namespace {
 
@@ -56,7 +59,7 @@
     return 1;
 }
 int socketRead(BIO* bio, char* buf, int size) {
-    android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+    borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
     int ret = TEMP_FAILURE_RETRY(::recv(fd.get(), buf, size, MSG_NOSIGNAL));
     BIO_clear_retry_flags(bio);
     if (errno == EAGAIN || errno == EWOULDBLOCK) {
@@ -66,7 +69,7 @@
 }
 
 int socketWrite(BIO* bio, const char* buf, int size) {
-    android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+    borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
     int ret = TEMP_FAILURE_RETRY(::send(fd.get(), buf, size, MSG_NOSIGNAL));
     BIO_clear_retry_flags(bio);
     if (errno == EAGAIN || errno == EWOULDBLOCK) {
@@ -76,13 +79,13 @@
 }
 
 long socketCtrl(BIO* bio, int cmd, long num, void*) { // NOLINT
-    android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+    borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
     if (cmd == BIO_CTRL_FLUSH) return 1;
     LOG_ALWAYS_FATAL("sockCtrl(fd=%d, %d, %ld)", fd.get(), cmd, num);
     return 0;
 }
 
-bssl::UniquePtr<BIO> newSocketBio(android::base::borrowed_fd fd) {
+bssl::UniquePtr<BIO> newSocketBio(borrowed_fd fd) {
     static const BIO_METHOD* gMethods = ([] {
         auto methods = BIO_meth_new(BIO_get_new_index(), "socket_no_signal");
         LOG_ALWAYS_FATAL_IF(0 == BIO_meth_set_write(methods, socketWrite), "BIO_meth_set_write");
@@ -289,12 +292,11 @@
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override;
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override;
+            std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
 
     bool isWaiting() override { return mSocket.isInPollingState(); };
 
@@ -325,7 +327,7 @@
 status_t RpcTransportTls::interruptableWriteFully(
         FdTrigger* fdTrigger, iovec* iovs, int niovs,
         const std::optional<SmallFunction<status_t()>>& altPoll,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+        const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     (void)ancillaryFds;
 
     MAYBE_WAIT_IN_FLAKE_MODE;
@@ -371,7 +373,7 @@
 status_t RpcTransportTls::interruptableReadFully(
         FdTrigger* fdTrigger, iovec* iovs, int niovs,
         const std::optional<SmallFunction<status_t()>>& altPoll,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     (void)ancillaryFds;
 
     MAYBE_WAIT_IN_FLAKE_MODE;
diff --git a/libs/binder/RpcTransportUtils.h b/libs/binder/RpcTransportUtils.h
index a0e502e..fcf6675 100644
--- a/libs/binder/RpcTransportUtils.h
+++ b/libs/binder/RpcTransportUtils.h
@@ -15,7 +15,7 @@
  */
 #pragma once
 
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
 #include <poll.h>
 
 #include "FdTrigger.h"
diff --git a/libs/binder/RpcTrusty.cpp b/libs/binder/RpcTrusty.cpp
index 2f2fe7d..a445196 100644
--- a/libs/binder/RpcTrusty.cpp
+++ b/libs/binder/RpcTrusty.cpp
@@ -16,14 +16,14 @@
 
 #define LOG_TAG "RpcTrusty"
 
-#include <android-base/unique_fd.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcTransportTipcAndroid.h>
+#include <binder/unique_fd.h>
 #include <trusty/tipc.h>
 
 namespace android {
 
-using android::base::unique_fd;
+using android::binder::unique_fd;
 
 sp<RpcSession> RpcTrustyConnectWithSessionInitializer(
         const char* device, const char* port,
diff --git a/libs/binder/UtilsHost.cpp b/libs/binder/UtilsHost.cpp
index 3db038f..ae1a6c4 100644
--- a/libs/binder/UtilsHost.cpp
+++ b/libs/binder/UtilsHost.cpp
@@ -25,10 +25,13 @@
 
 #include <log/log.h>
 
+#include "FdUtils.h"
 #include "Utils.h"
 
 namespace android {
 
+using android::binder::unique_fd;
+
 CommandResult::~CommandResult() {
     if (!pid.has_value()) return;
     if (*pid == 0) {
@@ -83,13 +86,13 @@
     argv.push_back(nullptr);
 
     CommandResult ret;
-    android::base::unique_fd outWrite;
-    if (!android::base::Pipe(&ret.outPipe, &outWrite)) {
+    unique_fd outWrite;
+    if (!binder::Pipe(&ret.outPipe, &outWrite)) {
         PLOGE("pipe() for outPipe");
         return {};
     }
-    android::base::unique_fd errWrite;
-    if (!android::base::Pipe(&ret.errPipe, &errWrite)) {
+    unique_fd errWrite;
+    if (!binder::Pipe(&ret.errPipe, &errWrite)) {
         PLOGE("pipe() for errPipe");
         return {};
     }
@@ -120,7 +123,7 @@
     errWrite.reset();
     ret.pid = pid;
 
-    auto handlePoll = [](android::base::unique_fd* fd, const pollfd* pfd, std::string* s) {
+    auto handlePoll = [](unique_fd* fd, const pollfd* pfd, std::string* s) {
         if (!fd->ok()) return true;
         if (pfd->revents & POLLIN) {
             char buf[1024];
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
index 5de0980..b582f17 100644
--- a/libs/binder/UtilsHost.h
+++ b/libs/binder/UtilsHost.h
@@ -23,7 +23,7 @@
 #include <vector>
 
 #include <android-base/macros.h>
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 
 /**
@@ -46,8 +46,8 @@
     std::string stdoutStr;
     std::string stderrStr;
 
-    android::base::unique_fd outPipe;
-    android::base::unique_fd errPipe;
+    binder::unique_fd outPipe;
+    binder::unique_fd errPipe;
 
     CommandResult() = default;
     CommandResult(CommandResult&& other) noexcept { (*this) = std::move(other); }
diff --git a/libs/binder/file.cpp b/libs/binder/file.cpp
new file mode 100644
index 0000000..bac667e
--- /dev/null
+++ b/libs/binder/file.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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 "file.h"
+
+#ifdef BINDER_NO_LIBBASE
+
+#include <stdint.h>
+
+// clang-format off
+
+namespace android::binder {
+
+bool ReadFully(borrowed_fd fd, void* data, size_t byte_count) {
+  uint8_t* p = reinterpret_cast<uint8_t*>(data);
+  size_t remaining = byte_count;
+  while (remaining > 0) {
+    ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), p, remaining));
+    if (n == 0) {  // EOF
+      errno = ENODATA;
+      return false;
+    }
+    if (n == -1) return false;
+    p += n;
+    remaining -= n;
+  }
+  return true;
+}
+
+bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count) {
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+  size_t remaining = byte_count;
+  while (remaining > 0) {
+    ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, remaining));
+    if (n == -1) return false;
+    p += n;
+    remaining -= n;
+  }
+  return true;
+}
+
+}  // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/file.h b/libs/binder/file.h
new file mode 100644
index 0000000..bcbfa31
--- /dev/null
+++ b/libs/binder/file.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#ifndef BINDER_NO_LIBBASE
+
+#include <android-base/file.h>
+
+namespace android::binder {
+using android::base::ReadFully;
+using android::base::WriteFully;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <binder/unique_fd.h>
+
+#include <string_view>
+
+namespace android::binder {
+
+bool ReadFully(borrowed_fd fd, void* data, size_t byte_count);
+bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count);
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index 744da0f..7a65ff4 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -102,7 +102,7 @@
     // to another process.
     void setParceled();
 
-    [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd clientFd,
+    [[nodiscard]] status_t setRpcClientDebug(binder::unique_fd clientFd,
                                              const sp<IBinder>& keepAliveBinder);
 
 protected:
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index d78ea0d..89a4d27 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -16,9 +16,9 @@
 
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/IBinder.h>
 #include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
 
 #include <map>
 #include <optional>
@@ -94,7 +94,7 @@
 
     // Start recording transactions to the unique_fd.
     // See RecordedTransaction.h for more details.
-    status_t startRecordingBinder(const android::base::unique_fd& fd);
+    status_t startRecordingBinder(const binder::unique_fd& fd);
     // Stop the current recording.
     status_t stopRecordingBinder();
 
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index e75d548..dad9a17 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
@@ -175,7 +175,7 @@
      *
      * On death of @a keepAliveBinder, the RpcServer shuts down.
      */
-    [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd socketFd,
+    [[nodiscard]] status_t setRpcClientDebug(binder::unique_fd socketFd,
                                              const sp<IBinder>& keepAliveBinder);
 
     // NOLINTNEXTLINE(google-default-arguments)
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index dc572ac..ac845bc 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -119,8 +119,8 @@
                   "The preferred way to add interfaces is to define "   \
                   "an .aidl file to auto-generate the interface. If "   \
                   "an interface must be manually written, add its "     \
-                  "name to the whitelist.");                            \
-    DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)    \
+                  "name to the allowlist.");                            \
+    DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)
 
 #else
 
@@ -305,10 +305,10 @@
   return equals(a + 1, b + 1);
 }
 
-constexpr bool inList(const char* a, const char* const* whitelist) {
-  if (*whitelist == nullptr) return false;
-  if (equals(a, *whitelist)) return true;
-  return inList(a, whitelist + 1);
+constexpr bool inList(const char* a, const char* const* allowlist) {
+  if (*allowlist == nullptr) return false;
+  if (equals(a, *allowlist)) return true;
+  return inList(a, allowlist + 1);
 }
 
 constexpr bool allowedManualInterface(const char* name) {
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 6961abc..09da6e3 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -25,7 +25,7 @@
 #include <variant>
 #include <vector>
 
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
 #ifndef BINDER_DISABLE_NATIVE_HANDLE
 #include <cutils/native_handle.h>
 #endif
@@ -354,17 +354,16 @@
     // Place a file descriptor into the parcel.  This will not affect the
     // semantics of the smart file descriptor. A new descriptor will be
     // created, and will be closed when the parcel is destroyed.
-    status_t            writeUniqueFileDescriptor(
-                            const base::unique_fd& fd);
+    status_t writeUniqueFileDescriptor(const binder::unique_fd& fd);
 
     // Place a vector of file desciptors into the parcel. Each descriptor is
     // dup'd as in writeDupFileDescriptor
-    status_t            writeUniqueFileDescriptorVector(
-                            const std::optional<std::vector<base::unique_fd>>& val);
-    status_t            writeUniqueFileDescriptorVector(
-                            const std::unique_ptr<std::vector<base::unique_fd>>& val) __attribute__((deprecated("use std::optional version instead")));
-    status_t            writeUniqueFileDescriptorVector(
-                            const std::vector<base::unique_fd>& val);
+    status_t writeUniqueFileDescriptorVector(
+            const std::optional<std::vector<binder::unique_fd>>& val);
+    status_t writeUniqueFileDescriptorVector(
+            const std::unique_ptr<std::vector<binder::unique_fd>>& val)
+            __attribute__((deprecated("use std::optional version instead")));
+    status_t writeUniqueFileDescriptorVector(const std::vector<binder::unique_fd>& val);
 
     // Writes a blob to the parcel.
     // If the blob is small, then it is stored in-place, otherwise it is
@@ -579,20 +578,17 @@
     int                 readParcelFileDescriptor() const;
 
     // Retrieve a smart file descriptor from the parcel.
-    status_t            readUniqueFileDescriptor(
-                            base::unique_fd* val) const;
+    status_t readUniqueFileDescriptor(binder::unique_fd* val) const;
 
     // Retrieve a Java "parcel file descriptor" from the parcel.
-    status_t            readUniqueParcelFileDescriptor(base::unique_fd* val) const;
-
+    status_t readUniqueParcelFileDescriptor(binder::unique_fd* val) const;
 
     // Retrieve a vector of smart file descriptors from the parcel.
-    status_t            readUniqueFileDescriptorVector(
-                            std::optional<std::vector<base::unique_fd>>* val) const;
-    status_t            readUniqueFileDescriptorVector(
-                            std::unique_ptr<std::vector<base::unique_fd>>* val) const __attribute__((deprecated("use std::optional version instead")));
-    status_t            readUniqueFileDescriptorVector(
-                            std::vector<base::unique_fd>* val) const;
+    status_t readUniqueFileDescriptorVector(
+            std::optional<std::vector<binder::unique_fd>>* val) const;
+    status_t readUniqueFileDescriptorVector(std::unique_ptr<std::vector<binder::unique_fd>>* val)
+            const __attribute__((deprecated("use std::optional version instead")));
+    status_t readUniqueFileDescriptorVector(std::vector<binder::unique_fd>* val) const;
 
     // Reads a blob from the parcel.
     // The caller should call release() on the blob after reading its contents.
@@ -629,7 +625,7 @@
     status_t rpcSetDataReference(
             const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
             const uint32_t* objectTable, size_t objectTableSize,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds,
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
             release_func relFunc);
 
     status_t            finishWrite(size_t len);
@@ -706,7 +702,7 @@
     // 5) Nullable objects contained in std::optional, std::unique_ptr, or std::shared_ptr.
     //
     // And active objects from the Android ecosystem such as:
-    // 6) File descriptors, base::unique_fd (kernel object handles)
+    // 6) File descriptors, unique_fd (kernel object handles)
     // 7) Binder objects, sp<IBinder> (active Android RPC handles)
     //
     // Objects from (1) through (5) serialize into the mData buffer.
@@ -957,9 +953,7 @@
         return writeUtf8AsUtf16(t);
     }
 
-    status_t writeData(const base::unique_fd& t) {
-        return writeUniqueFileDescriptor(t);
-    }
+    status_t writeData(const binder::unique_fd& t) { return writeUniqueFileDescriptor(t); }
 
     status_t writeData(const Parcelable& t) {  // std::is_base_of_v<Parcelable, T>
         // implemented here. writeParcelable() calls this.
@@ -1106,9 +1100,7 @@
         return readUtf8FromUtf16(t);
     }
 
-    status_t readData(base::unique_fd* t) const {
-        return readUniqueFileDescriptor(t);
-    }
+    status_t readData(binder::unique_fd* t) const { return readUniqueFileDescriptor(t); }
 
     status_t readData(Parcelable* t) const { // std::is_base_of_v<Parcelable, T>
         // implemented here. readParcelable() calls this.
@@ -1328,7 +1320,7 @@
         // same order as `mObjectPositions`.
         //
         // Boxed to save space. Lazy allocated.
-        std::unique_ptr<std::vector<std::variant<base::unique_fd, base::borrowed_fd>>> mFds;
+        std::unique_ptr<std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>> mFds;
     };
     std::variant<KernelFields, RpcFields> mVariantFields;
 
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 08d8e43..c4ef354 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -16,9 +16,9 @@
 
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
+#include <binder/unique_fd.h>
 
 namespace android {
 namespace os {
@@ -29,14 +29,14 @@
 class ParcelFileDescriptor : public android::Parcelable {
 public:
     ParcelFileDescriptor();
-    explicit ParcelFileDescriptor(android::base::unique_fd fd);
+    explicit ParcelFileDescriptor(binder::unique_fd fd);
     ParcelFileDescriptor(ParcelFileDescriptor&& other) noexcept : mFd(std::move(other.mFd)) { }
     ParcelFileDescriptor& operator=(ParcelFileDescriptor&& other) noexcept = default;
     ~ParcelFileDescriptor() override;
 
     int get() const { return mFd.get(); }
-    android::base::unique_fd release() { return std::move(mFd); }
-    void reset(android::base::unique_fd fd = android::base::unique_fd()) { mFd = std::move(fd); }
+    binder::unique_fd release() { return std::move(mFd); }
+    void reset(binder::unique_fd fd = binder::unique_fd()) { mFd = std::move(fd); }
 
     // android::Parcelable override:
     android::status_t writeToParcel(android::Parcel* parcel) const override;
@@ -62,7 +62,7 @@
         return mFd.get() >= rhs.mFd.get();
     }
 private:
-    android::base::unique_fd mFd;
+    binder::unique_fd mFd;
 };
 
 } // namespace os
diff --git a/libs/binder/include/binder/RecordedTransaction.h b/libs/binder/include/binder/RecordedTransaction.h
index eb765fe..505c199 100644
--- a/libs/binder/include/binder/RecordedTransaction.h
+++ b/libs/binder/include/binder/RecordedTransaction.h
@@ -16,8 +16,8 @@
 
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/Parcel.h>
+#include <binder/unique_fd.h>
 #include <mutex>
 
 namespace android {
@@ -31,7 +31,8 @@
 class RecordedTransaction {
 public:
     // Filled with the first transaction from fd.
-    static std::optional<RecordedTransaction> fromFile(const android::base::unique_fd& fd);
+
+    static std::optional<RecordedTransaction> fromFile(const binder::unique_fd& fd);
     // Filled with the arguments.
     static std::optional<RecordedTransaction> fromDetails(const String16& interfaceName,
                                                           uint32_t code, uint32_t flags,
@@ -39,7 +40,7 @@
                                                           const Parcel& reply, status_t err);
     RecordedTransaction(RecordedTransaction&& t) noexcept;
 
-    [[nodiscard]] status_t dumpToFile(const android::base::unique_fd& fd) const;
+    [[nodiscard]] status_t dumpToFile(const binder::unique_fd& fd) const;
 
     const std::string& getInterfaceName() const;
     uint32_t getCode() const;
@@ -53,8 +54,8 @@
 private:
     RecordedTransaction() = default;
 
-    android::status_t writeChunk(const android::base::borrowed_fd, uint32_t chunkType,
-                                 size_t byteCount, const uint8_t* data) const;
+    android::status_t writeChunk(const binder::borrowed_fd, uint32_t chunkType, size_t byteCount,
+                                 const uint8_t* data) const;
 
 #pragma clang diagnostic push
 #pragma clang diagnostic error "-Wpadded"
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index 2153f16..a07880d 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -15,11 +15,11 @@
  */
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/IBinder.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcThreads.h>
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
@@ -59,7 +59,7 @@
      * to RpcSession::setupUnixDomainSocketBootstrapClient. Multiple client
      * session can be created from the client end of the pair.
      */
-    [[nodiscard]] status_t setupUnixDomainSocketBootstrapServer(base::unique_fd serverFd);
+    [[nodiscard]] status_t setupUnixDomainSocketBootstrapServer(binder::unique_fd serverFd);
 
     /**
      * This represents a session for responses, e.g.:
@@ -79,7 +79,7 @@
      * This method is used in the libbinder_rpc_unstable API
      * RunInitUnixDomainRpcServer().
      */
-    [[nodiscard]] status_t setupRawSocketServer(base::unique_fd socket_fd);
+    [[nodiscard]] status_t setupRawSocketServer(binder::unique_fd socket_fd);
 
     /**
      * Creates an RPC server binding to the given CID at the given port.
@@ -111,13 +111,13 @@
     /**
      * If hasServer(), return the server FD. Otherwise return invalid FD.
      */
-    [[nodiscard]] base::unique_fd releaseServer();
+    [[nodiscard]] binder::unique_fd releaseServer();
 
     /**
      * Set up server using an external FD previously set up by releaseServer().
      * Return false if there's already a server.
      */
-    [[nodiscard]] status_t setupExternalServer(base::unique_fd serverFd);
+    [[nodiscard]] status_t setupExternalServer(binder::unique_fd serverFd);
 
     /**
      * This must be called before adding a client session. This corresponds
@@ -193,7 +193,7 @@
      *
      * The only argument is a successfully created file descriptor, not bound to an address yet.
      */
-    void setServerSocketModifier(std::function<void(base::borrowed_fd)>&& modifier);
+    void setServerSocketModifier(std::function<void(binder::borrowed_fd)>&& modifier);
 
     /**
      * See RpcTransportCtx::getCertificate
@@ -249,7 +249,7 @@
     void onSessionIncomingThreadEnded() override;
 
     status_t setupExternalServer(
-            base::unique_fd serverFd,
+            binder::unique_fd serverFd,
             std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn);
 
     static constexpr size_t kRpcAddressSize = 128;
@@ -279,7 +279,7 @@
     wp<IBinder> mRootObjectWeak;
     std::function<sp<IBinder>(wp<RpcSession>, const void*, size_t)> mRootObjectFactory;
     std::function<bool(const void*, size_t)> mConnectionFilter;
-    std::function<void(base::borrowed_fd)> mServerSocketModifier;
+    std::function<void(binder::borrowed_fd)> mServerSocketModifier;
     std::map<std::vector<uint8_t>, sp<RpcSession>> mSessions;
     std::unique_ptr<FdTrigger> mShutdownTrigger;
     RpcConditionVariable mShutdownCv;
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index e3805ac..11fbde9 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -15,10 +15,10 @@
  */
 #pragma once
 
-#include <android-base/unique_fd.h>
 #include <binder/IBinder.h>
 #include <binder/RpcThreads.h>
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
@@ -123,7 +123,7 @@
     /**
      * Connects to an RPC server over a nameless Unix domain socket pair.
      */
-    [[nodiscard]] status_t setupUnixDomainSocketBootstrapClient(base::unique_fd bootstrap);
+    [[nodiscard]] status_t setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
 
     /**
      * Connects to an RPC server at the CVD & port.
@@ -145,8 +145,8 @@
      *
      * For future compatibility, 'request' should not reference any stack data.
      */
-    [[nodiscard]] status_t setupPreconnectedClient(base::unique_fd fd,
-                                                   std::function<base::unique_fd()>&& request);
+    [[nodiscard]] status_t setupPreconnectedClient(binder::unique_fd fd,
+                                                   std::function<binder::unique_fd()>&& request);
 
     /**
      * For debugging!
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index 115a173..a50cdc1 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -25,12 +25,12 @@
 #include <variant>
 #include <vector>
 
-#include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 
 #include <binder/Functional.h>
 #include <binder/RpcCertificateFormat.h>
 #include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
 
 #include <sys/uio.h>
 
@@ -87,11 +87,12 @@
     [[nodiscard]] virtual status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) = 0;
+            const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
+                    ancillaryFds) = 0;
     [[nodiscard]] virtual status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
             const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) = 0;
+            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds) = 0;
 
     /**
      *  Check whether any threads are blocked while polling the transport
@@ -177,10 +178,10 @@
     void setPollingState(bool state) const { isPolling = state; }
 
 public:
-    base::unique_fd fd;
+    binder::unique_fd fd;
 
     RpcTransportFd() = default;
-    explicit RpcTransportFd(base::unique_fd &&descriptor)
+    explicit RpcTransportFd(binder::unique_fd&& descriptor)
           : isPolling(false), fd(std::move(descriptor)) {}
 
     RpcTransportFd(RpcTransportFd &&transportFd) noexcept
@@ -192,7 +193,7 @@
         return *this;
     }
 
-    RpcTransportFd &operator=(base::unique_fd &&descriptor) noexcept {
+    RpcTransportFd& operator=(binder::unique_fd&& descriptor) noexcept {
         fd = std::move(descriptor);
         isPolling = false;
         return *this;
diff --git a/libs/binder/include/binder/unique_fd.h b/libs/binder/include/binder/unique_fd.h
new file mode 100644
index 0000000..439b8a2
--- /dev/null
+++ b/libs/binder/include/binder/unique_fd.h
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#ifndef BINDER_NO_LIBBASE
+
+#include <android-base/unique_fd.h>
+
+namespace android::binder {
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <errno.h>
+#include <fcntl.h> // not needed for unique_fd, but a lot of users depend on open(3)
+#include <unistd.h>
+
+namespace android::binder {
+
+// Container for a file descriptor that automatically closes the descriptor as
+// it goes out of scope.
+//
+//      unique_fd ufd(open("/some/path", "r"));
+//      if (!ufd.ok()) return error;
+//
+//      // Do something useful with ufd.get(), possibly including early 'return'.
+//
+//      return 0; // Descriptor is closed for you.
+//
+class unique_fd final {
+public:
+    unique_fd() {}
+
+    explicit unique_fd(int fd) { reset(fd); }
+    ~unique_fd() { reset(); }
+
+    unique_fd(const unique_fd&) = delete;
+    void operator=(const unique_fd&) = delete;
+    unique_fd(unique_fd&& other) noexcept { reset(other.release()); }
+    unique_fd& operator=(unique_fd&& s) noexcept {
+        int fd = s.fd_;
+        s.fd_ = -1;
+        reset(fd);
+        return *this;
+    }
+
+    [[clang::reinitializes]] void reset(int new_value = -1) {
+        int previous_errno = errno;
+
+        if (fd_ != -1) {
+            ::close(fd_);
+        }
+
+        fd_ = new_value;
+        errno = previous_errno;
+    }
+
+    int get() const { return fd_; }
+
+    bool ok() const { return get() >= 0; }
+
+    [[nodiscard]] int release() {
+        int ret = fd_;
+        fd_ = -1;
+        return ret;
+    }
+
+private:
+    int fd_ = -1;
+};
+
+// A wrapper type that can be implicitly constructed from either int or
+// unique_fd. This supports cases where you don't actually own the file
+// descriptor, and can't take ownership, but are temporarily acting as if
+// you're the owner.
+//
+// One example would be a function that needs to also allow
+// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
+// that's using a file descriptor that's actually owned by a
+// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
+// would like to enforce this weaker sense of "temporary ownership".
+//
+// If you think of unique_fd as being like std::string in that represents
+// ownership, borrowed_fd is like std::string_view (and int is like const
+// char*).
+struct borrowed_fd {
+    /* implicit */ borrowed_fd(int fd) : fd_(fd) {}                      // NOLINT
+    /* implicit */ borrowed_fd(const unique_fd& ufd) : fd_(ufd.get()) {} // NOLINT
+
+    int get() const { return fd_; }
+
+private:
+    int fd_ = -1;
+};
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 118409e..ddd82e8 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -16,10 +16,10 @@
 
 #include <binder_rpc_unstable.hpp>
 
-#include <android-base/unique_fd.h>
 #include <android/binder_libbinder.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
+#include <binder/unique_fd.h>
 #include <cutils/sockets.h>
 #include <linux/vm_sockets.h>
 
@@ -29,7 +29,7 @@
 using android::sp;
 using android::status_t;
 using android::statusToString;
-using android::base::unique_fd;
+using android::binder::unique_fd;
 
 // Opaque handle for RpcServer.
 struct ARpcServer {};
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index c15bcf9..88ce5f4 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include <android-base/unique_fd.h>
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
 #include <binder/Parcel.h>
 #include <binder/ParcelFileDescriptor.h>
+#include <binder/unique_fd.h>
 #include <inttypes.h>
 #include <utils/Unicode.h>
 
@@ -32,7 +32,7 @@
 using ::android::Parcel;
 using ::android::sp;
 using ::android::status_t;
-using ::android::base::unique_fd;
+using ::android::binder::unique_fd;
 using ::android::os::ParcelFileDescriptor;
 
 template <typename T>
diff --git a/libs/binder/rust/tests/serialization.cpp b/libs/binder/rust/tests/serialization.cpp
index 3f59dab..0cdf8c5 100644
--- a/libs/binder/rust/tests/serialization.cpp
+++ b/libs/binder/rust/tests/serialization.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#include "serialization.hpp"
+#include "../../FdUtils.h"
+#include "../../tests/FileUtils.h"
+
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_libbinder.h>
 #include <binder/IServiceManager.h>
@@ -24,8 +28,6 @@
 #include <gtest/gtest.h>
 #include <utils/Errors.h>
 #include <utils/String16.h>
-#include "android-base/file.h"
-#include "serialization.hpp"
 
 #include <cmath>
 #include <cstdint>
@@ -34,7 +36,7 @@
 
 using namespace std;
 using namespace android;
-using android::base::unique_fd;
+using android::binder::unique_fd;
 using android::os::ParcelFileDescriptor;
 
 // defined in Rust
@@ -349,7 +351,7 @@
 
 TEST_F(SerializationTest, SerializeFileDescriptor) {
     unique_fd out_file, in_file;
-    ASSERT_TRUE(base::Pipe(&out_file, &in_file));
+    ASSERT_TRUE(binder::Pipe(&out_file, &in_file));
 
     vector<ParcelFileDescriptor> file_descriptors;
     file_descriptors.push_back(ParcelFileDescriptor(std::move(out_file)));
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index f2693dd..18b178b 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -17,9 +17,9 @@
 #include <sysexits.h>
 #include <unistd.h>
 
+#include <filesystem>
 #include <iostream>
 
-#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android/debug/BnAdbCallback.h>
@@ -30,6 +30,8 @@
 #include <binder/ProcessState.h>
 #include <binder/RpcServer.h>
 
+#include "file.h"
+
 using android::BBinder;
 using android::defaultServiceManager;
 using android::OK;
@@ -38,7 +40,6 @@
 using android::status_t;
 using android::statusToString;
 using android::String16;
-using android::base::Basename;
 using android::base::GetBoolProperty;
 using android::base::InitLogging;
 using android::base::LogdLogger;
@@ -53,8 +54,8 @@
 using ServiceRetriever = decltype(&android::IServiceManager::checkService);
 using android::debug::IAdbManager;
 
-int Usage(const char* program) {
-    auto basename = Basename(program);
+int Usage(std::filesystem::path program) {
+    auto basename = program.filename();
     // clang-format off
     LOG(ERROR) << R"(dispatch calls to RPC service.
 Usage:
@@ -253,7 +254,8 @@
         mLogdLogger(id, severity, tag, file, line, message);
         if (severity >= LogSeverity::WARNING) {
             std::cout << std::flush;
-            std::cerr << Basename(getprogname()) << ": " << message << std::endl;
+            auto progname = std::filesystem::path(getprogname()).filename();
+            std::cerr << progname << ": " << message << std::endl;
         }
     }
 
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index cd3e7c0..ba8fb39 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -172,6 +172,30 @@
     ],
 }
 
+cc_library_static {
+    name: "libbinder_test_utils",
+    host_supported: true,
+    vendor_available: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    defaults: [
+        "binder_test_defaults",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    srcs: [
+        "FileUtils.cpp",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
 cc_defaults {
     name: "binderRpcTest_common_defaults",
     host_supported: true,
@@ -185,6 +209,7 @@
     ],
 
     static_libs: [
+        "libbinder_test_utils",
         "libbinder_tls_static",
         "libbinder_tls_test_utils",
         "binderRpcTestIface-cpp",
diff --git a/libs/binder/tests/FileUtils.cpp b/libs/binder/tests/FileUtils.cpp
new file mode 100644
index 0000000..61509fe
--- /dev/null
+++ b/libs/binder/tests/FileUtils.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "FileUtils.h"
+
+#ifdef BINDER_NO_LIBBASE
+
+#include <sys/stat.h>
+#include <filesystem>
+
+#if defined(__APPLE__)
+#include <mach-o/dyld.h>
+#endif
+#if defined(_WIN32)
+#include <direct.h>
+#include <windows.h>
+#endif
+
+namespace android::binder {
+
+bool ReadFdToString(borrowed_fd fd, std::string* content) {
+    content->clear();
+
+    // Although original we had small files in mind, this code gets used for
+    // very large files too, where the std::string growth heuristics might not
+    // be suitable. https://code.google.com/p/android/issues/detail?id=258500.
+    struct stat sb;
+    if (fstat(fd.get(), &sb) != -1 && sb.st_size > 0) {
+        content->reserve(sb.st_size);
+    }
+
+    char buf[4096] __attribute__((__uninitialized__));
+    ssize_t n;
+    while ((n = TEMP_FAILURE_RETRY(read(fd.get(), &buf[0], sizeof(buf)))) > 0) {
+        content->append(buf, n);
+    }
+    return (n == 0) ? true : false;
+}
+
+bool WriteStringToFd(std::string_view content, borrowed_fd fd) {
+    const char* p = content.data();
+    size_t left = content.size();
+    while (left > 0) {
+        ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, left));
+        if (n == -1) {
+            return false;
+        }
+        p += n;
+        left -= n;
+    }
+    return true;
+}
+
+static std::filesystem::path GetExecutablePath2() {
+#if defined(__linux__)
+    return std::filesystem::read_symlink("/proc/self/exe");
+#elif defined(__APPLE__)
+    char path[PATH_MAX + 1];
+    uint32_t path_len = sizeof(path);
+    int rc = _NSGetExecutablePath(path, &path_len);
+    if (rc < 0) {
+        std::unique_ptr<char> path_buf(new char[path_len]);
+        _NSGetExecutablePath(path_buf.get(), &path_len);
+        return path_buf.get();
+    }
+    return path;
+#elif defined(_WIN32)
+    char path[PATH_MAX + 1];
+    DWORD result = GetModuleFileName(NULL, path, sizeof(path) - 1);
+    if (result == 0 || result == sizeof(path) - 1) return "";
+    path[PATH_MAX - 1] = 0;
+    return path;
+#elif defined(__EMSCRIPTEN__)
+    abort();
+#else
+#error unknown OS
+#endif
+}
+
+std::string GetExecutableDirectory() {
+    return GetExecutablePath2().parent_path();
+}
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/tests/FileUtils.h b/libs/binder/tests/FileUtils.h
new file mode 100644
index 0000000..2cbe5e7
--- /dev/null
+++ b/libs/binder/tests/FileUtils.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "../file.h"
+
+#ifndef BINDER_NO_LIBBASE
+
+namespace android::binder {
+using android::base::GetExecutableDirectory;
+using android::base::ReadFdToString;
+using android::base::WriteStringToFd;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <binder/unique_fd.h>
+
+#include <string_view>
+
+#if !defined(_WIN32) && !defined(O_BINARY)
+/** Windows needs O_BINARY, but Unix never mangles line endings. */
+#define O_BINARY 0
+#endif
+
+namespace android::binder {
+
+bool ReadFdToString(borrowed_fd fd, std::string* content);
+bool WriteStringToFd(std::string_view content, borrowed_fd fd);
+
+std::string GetExecutableDirectory();
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index f3969f1..cb1a1ee 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -30,7 +30,6 @@
 #include <android-base/properties.h>
 #include <android-base/result-gmock.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
 #include <binder/Functional.h>
@@ -39,6 +38,7 @@
 #include <binder/IServiceManager.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
+#include <binder/unique_fd.h>
 #include <utils/Flattenable.h>
 
 #include <linux/sched.h>
@@ -57,6 +57,7 @@
 using namespace std::chrono_literals;
 using android::base::testing::HasValue;
 using android::base::testing::Ok;
+using android::binder::unique_fd;
 using testing::ExplainMatchResult;
 using testing::Matcher;
 using testing::Not;
@@ -847,7 +848,7 @@
         writebuf[i] = i;
     }
 
-    android::base::unique_fd read_end, write_end;
+    unique_fd read_end, write_end;
     {
         int pipefd[2];
         ASSERT_EQ(0, pipe2(pipefd, O_NONBLOCK));
@@ -1177,7 +1178,7 @@
     Parcel reply;
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_GET_NON_BLOCKING_FD, {} /*data*/, &reply),
                 StatusEq(NO_ERROR));
-    base::unique_fd fd;
+    unique_fd fd;
     EXPECT_THAT(reply.readUniqueFileDescriptor(&fd), StatusEq(OK));
 
     const int result = fcntl(fd.get(), F_GETFL);
@@ -1486,7 +1487,7 @@
         BinderLibTest::SetUp();
     }
 
-    std::tuple<android::base::unique_fd, unsigned int> CreateSocket() {
+    std::tuple<unique_fd, unsigned int> CreateSocket() {
         auto rpcServer = RpcServer::make();
         EXPECT_NE(nullptr, rpcServer);
         if (rpcServer == nullptr) return {};
@@ -1553,7 +1554,7 @@
 TEST_P(BinderLibRpcTestP, SetRpcClientDebugNoFd) {
     auto binder = GetService();
     ASSERT_TRUE(binder != nullptr);
-    EXPECT_THAT(binder->setRpcClientDebug(android::base::unique_fd(), sp<BBinder>::make()),
+    EXPECT_THAT(binder->setRpcClientDebug(unique_fd(), sp<BBinder>::make()),
                 Debuggable(StatusEq(BAD_VALUE)));
 }
 
@@ -1823,7 +1824,7 @@
                 int ret;
                 int32_t size;
                 const void *buf;
-                android::base::unique_fd fd;
+                unique_fd fd;
 
                 ret = data.readUniqueParcelFileDescriptor(&fd);
                 if (ret != NO_ERROR) {
@@ -1888,7 +1889,7 @@
                     ALOGE("Could not make socket non-blocking: %s", strerror(errno));
                     return UNKNOWN_ERROR;
                 }
-                base::unique_fd out(sockets[0]);
+                unique_fd out(sockets[0]);
                 status_t writeResult = reply->writeUniqueFileDescriptor(out);
                 if (writeResult != NO_ERROR) {
                     ALOGE("Could not write unique_fd");
diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp
index 0a0dae0..34fc43f 100644
--- a/libs/binder/tests/binderParcelUnitTest.cpp
+++ b/libs/binder/tests/binderParcelUnitTest.cpp
@@ -29,8 +29,8 @@
 using android::status_t;
 using android::String16;
 using android::String8;
-using android::base::unique_fd;
 using android::binder::Status;
+using android::binder::unique_fd;
 
 TEST(Parcel, NonNullTerminatedString8) {
     String8 kTestString = String8("test-is-good");
diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp
index d08a9bb..73c0a94 100644
--- a/libs/binder/tests/binderRecordReplayTest.cpp
+++ b/libs/binder/tests/binderRecordReplayTest.cpp
@@ -15,15 +15,14 @@
  */
 
 #include <BnBinderRecordReplayTest.h>
-#include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android-base/unique_fd.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
 
 #include <fuzzbinder/libbinder_driver.h>
 #include <fuzzer/FuzzedDataProvider.h>
@@ -33,11 +32,14 @@
 
 #include <sys/prctl.h>
 
+#include "../file.h"
 #include "parcelables/SingleDataParcelable.h"
 
 using namespace android;
 using android::generateSeedsFromRecording;
+using android::binder::borrowed_fd;
 using android::binder::Status;
+using android::binder::unique_fd;
 using android::binder::debug::RecordedTransaction;
 using parcelables::SingleDataParcelable;
 
@@ -91,7 +93,7 @@
     GENERATE_GETTER_SETTER(SingleDataParcelableArray, std::vector<SingleDataParcelable>);
 };
 
-std::vector<uint8_t> retrieveData(base::borrowed_fd fd) {
+std::vector<uint8_t> retrieveData(borrowed_fd fd) {
     struct stat fdStat;
     EXPECT_TRUE(fstat(fd.get(), &fdStat) != -1);
     EXPECT_TRUE(fdStat.st_size != 0);
@@ -103,8 +105,8 @@
 }
 
 void replayFuzzService(const sp<BpBinder>& binder, const RecordedTransaction& transaction) {
-    base::unique_fd seedFd(open("/data/local/tmp/replayFuzzService",
-                                O_RDWR | O_CREAT | O_CLOEXEC | O_TRUNC, 0666));
+    unique_fd seedFd(open("/data/local/tmp/replayFuzzService",
+                          O_RDWR | O_CREAT | O_CLOEXEC | O_TRUNC, 0666));
     ASSERT_TRUE(seedFd.ok());
 
     // generate corpus from this transaction.
@@ -148,8 +150,8 @@
                       Status (IBinderRecordReplayTest::*get)(U*), U changedValue) {
         auto replayFunctions = {&replayBinder, &replayFuzzService};
         for (auto replayFunc : replayFunctions) {
-            base::unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec",
-                                    O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+            unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec",
+                              O_RDWR | O_CREAT | O_CLOEXEC, 0666));
             ASSERT_TRUE(fd.ok());
 
             // record a transaction
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 30172cc..6eb78d0 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -20,7 +20,7 @@
 
 using android::Parcel;
 using android::status_t;
-using android::base::unique_fd;
+using android::binder::unique_fd;
 using android::binder::debug::RecordedTransaction;
 
 TEST(BinderRecordedTransaction, RoundTripEncoding) {
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 624edba..2769a88 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -25,6 +25,7 @@
 #include <thread>
 #include <type_traits>
 
+#include <dirent.h>
 #include <dlfcn.h>
 #include <poll.h>
 #include <sys/prctl.h>
@@ -41,6 +42,10 @@
 
 using namespace std::chrono_literals;
 using namespace std::placeholders;
+using android::binder::borrowed_fd;
+using android::binder::GetExecutableDirectory;
+using android::binder::ReadFdToString;
+using android::binder::unique_fd;
 using testing::AssertionFailure;
 using testing::AssertionResult;
 using testing::AssertionSuccess;
@@ -83,12 +88,11 @@
         mPid = other.mPid;
         other.mPid = 0;
     }
-    Process(const std::function<void(android::base::borrowed_fd /* writeEnd */,
-                                     android::base::borrowed_fd /* readEnd */)>& f) {
-        android::base::unique_fd childWriteEnd;
-        android::base::unique_fd childReadEnd;
-        if (!android::base::Pipe(&mReadEnd, &childWriteEnd, 0)) PLOGF("child write pipe failed");
-        if (!android::base::Pipe(&childReadEnd, &mWriteEnd, 0)) PLOGF("child read pipe failed");
+    Process(const std::function<void(borrowed_fd /* writeEnd */, borrowed_fd /* readEnd */)>& f) {
+        unique_fd childWriteEnd;
+        unique_fd childReadEnd;
+        if (!binder::Pipe(&mReadEnd, &childWriteEnd, 0)) PLOGF("child write pipe failed");
+        if (!binder::Pipe(&childReadEnd, &mWriteEnd, 0)) PLOGF("child read pipe failed");
         if (0 == (mPid = fork())) {
             // racey: assume parent doesn't crash before this is set
             prctl(PR_SET_PDEATHSIG, SIGHUP);
@@ -110,8 +114,8 @@
             }
         }
     }
-    android::base::borrowed_fd readEnd() { return mReadEnd; }
-    android::base::borrowed_fd writeEnd() { return mWriteEnd; }
+    borrowed_fd readEnd() { return mReadEnd; }
+    borrowed_fd writeEnd() { return mWriteEnd; }
 
     void setCustomExitStatusCheck(std::function<void(int wstatus)> f) {
         mCustomExitStatusCheck = std::move(f);
@@ -125,8 +129,8 @@
 private:
     std::function<void(int wstatus)> mCustomExitStatusCheck;
     pid_t mPid = 0;
-    android::base::unique_fd mReadEnd;
-    android::base::unique_fd mWriteEnd;
+    unique_fd mReadEnd;
+    unique_fd mWriteEnd;
 };
 
 static std::string allocateSocketAddress() {
@@ -142,10 +146,9 @@
     return vsockPort++;
 }
 
-static base::unique_fd initUnixSocket(std::string addr) {
+static unique_fd initUnixSocket(std::string addr) {
     auto socket_addr = UnixSocketAddress(addr.c_str());
-    base::unique_fd fd(
-            TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
+    unique_fd fd(TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
     if (!fd.ok()) PLOGF("initUnixSocket failed to create socket");
     if (0 != TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize()))) {
         PLOGF("initUnixSocket failed to bind");
@@ -204,8 +207,8 @@
     void terminate() override { host.terminate(); }
 };
 
-static base::unique_fd connectTo(const RpcSocketAddress& addr) {
-    base::unique_fd serverFd(
+static unique_fd connectTo(const RpcSocketAddress& addr) {
+    unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
     if (!serverFd.ok()) {
         PLOGF("Could not create socket %s", addr.toString().c_str());
@@ -218,15 +221,15 @@
 }
 
 #ifndef BINDER_RPC_TO_TRUSTY_TEST
-static base::unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
-    base::unique_fd sockClient, sockServer;
-    if (!base::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
+static unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
+    unique_fd sockClient, sockServer;
+    if (!binder::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
         PLOGF("Failed socketpair()");
     }
 
     int zero = 0;
     iovec iov{&zero, sizeof(zero)};
-    std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+    std::vector<std::variant<unique_fd, borrowed_fd>> fds;
     fds.emplace_back(std::move(sockServer));
 
     if (binder::os::sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
@@ -259,12 +262,12 @@
     bool singleThreaded = GetParam().singleThreaded;
     bool noKernel = GetParam().noKernel;
 
-    std::string path = android::base::GetExecutableDirectory();
+    std::string path = GetExecutableDirectory();
     auto servicePath =
             std::format("{}/binder_rpc_test_service{}{}", path,
                         singleThreaded ? "_single_threaded" : "", noKernel ? "_no_kernel" : "");
 
-    base::unique_fd bootstrapClientFd, socketFd;
+    unique_fd bootstrapClientFd, socketFd;
 
     auto addr = allocateSocketAddress();
     // Initializes the socket before the fork/exec.
@@ -273,13 +276,13 @@
     } else if (socketType == SocketType::UNIX_BOOTSTRAP) {
         // Do not set O_CLOEXEC, bootstrapServerFd needs to survive fork/exec.
         // This is because we cannot pass ParcelFileDescriptor over a pipe.
-        if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
+        if (!binder::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
             PLOGF("Failed socketpair()");
         }
     }
 
     auto ret = std::make_unique<LinuxProcessSession>(
-            Process([=](android::base::borrowed_fd writeEnd, android::base::borrowed_fd readEnd) {
+            Process([=](borrowed_fd writeEnd, borrowed_fd readEnd) {
                 if (socketType == SocketType::TIPC) {
                     // Trusty has a single persistent service
                     return;
@@ -374,7 +377,7 @@
                 break;
             case SocketType::UNIX_BOOTSTRAP:
                 status = session->setupUnixDomainSocketBootstrapClient(
-                        base::unique_fd(dup(bootstrapClientFd.get())));
+                        unique_fd(dup(bootstrapClientFd.get())));
                 break;
             case SocketType::VSOCK:
                 status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
@@ -391,14 +394,14 @@
                         // in case the service is slow to start
                         int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
                         if (tipcFd >= 0) {
-                            return android::base::unique_fd(tipcFd);
+                            return unique_fd(tipcFd);
                         }
                         usleep(50000);
                     }
-                    return android::base::unique_fd();
+                    return unique_fd();
 #else
                     LOG_ALWAYS_FATAL("Tried to connect to Trusty outside of vendor");
-                    return android::base::unique_fd();
+                    return unique_fd();
 #endif
                 });
                 break;
@@ -590,12 +593,12 @@
     android::os::ParcelFileDescriptor fdA;
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdA));
     std::string result;
-    ASSERT_TRUE(android::base::ReadFdToString(fdA.get(), &result));
+    ASSERT_TRUE(ReadFdToString(fdA.get(), &result));
     EXPECT_EQ(result, "a");
 
     android::os::ParcelFileDescriptor fdB;
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdB));
-    ASSERT_TRUE(android::base::ReadFdToString(fdB.get(), &result));
+    ASSERT_TRUE(ReadFdToString(fdB.get(), &result));
     EXPECT_EQ(result, "b");
 
     saturateThreadPool(kNumServerThreads, proc.rootIface);
@@ -952,7 +955,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    ASSERT_TRUE(android::base::ReadFdToString(out.get(), &result));
+    ASSERT_TRUE(ReadFdToString(out.get(), &result));
     ASSERT_EQ(result, "hello");
 }
 
@@ -982,7 +985,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    EXPECT_TRUE(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, "123abcd");
 }
 
@@ -1007,7 +1010,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    EXPECT_TRUE(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, std::string(253, 'a'));
 }
 
@@ -1152,7 +1155,7 @@
     // We don't need to enable TLS to know if vsock is supported.
     unsigned int vsockPort = allocateVsockPort();
 
-    android::base::unique_fd serverFd(
+    unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
 
     if (errno == EAFNOSUPPORT) {
@@ -1179,7 +1182,7 @@
     // to see if the kernel supports it. It's safe to use a blocking
     // connect because vsock sockets have a 2 second connection timeout,
     // and they return ETIMEDOUT after that.
-    android::base::unique_fd connectFd(
+    unique_fd connectFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
     LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
                         strerror(errno));
@@ -1193,7 +1196,7 @@
     ret = TEMP_FAILURE_RETRY(connect(connectFd.get(), reinterpret_cast<sockaddr*>(&connectAddr),
                                      sizeof(connectAddr)));
     if (ret != 0 && (errno == EAGAIN || errno == EINPROGRESS)) {
-        android::base::unique_fd acceptFd;
+        unique_fd acceptFd;
         while (true) {
             pollfd pfd[]{
                     {.fd = serverFd.get(), .events = POLLIN, .revents = 0},
@@ -1423,14 +1426,14 @@
 };
 
 TEST_P(BinderRpcServerOnly, SetExternalServerTest) {
-    base::unique_fd sink(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
+    unique_fd sink(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
     int sinkFd = sink.get();
     auto server = RpcServer::make(newTlsFactory(std::get<0>(GetParam())));
     ASSERT_TRUE(server->setProtocolVersion(std::get<1>(GetParam())));
     ASSERT_FALSE(server->hasServer());
     ASSERT_EQ(OK, server->setupExternalServer(std::move(sink)));
     ASSERT_TRUE(server->hasServer());
-    base::unique_fd retrieved = server->releaseServer();
+    unique_fd retrieved = server->releaseServer();
     ASSERT_FALSE(server->hasServer());
     ASSERT_EQ(sinkFd, retrieved.get());
 }
@@ -1476,12 +1479,12 @@
     // in the client half of the tests.
     using Param =
             std::tuple<SocketType, RpcSecurity, std::optional<RpcCertificateFormat>, uint32_t>;
-    using ConnectToServer = std::function<base::unique_fd()>;
+    using ConnectToServer = std::function<unique_fd()>;
 
     // A server that handles client socket connections.
     class Server {
     public:
-        using AcceptConnection = std::function<base::unique_fd(Server*)>;
+        using AcceptConnection = std::function<unique_fd(Server*)>;
 
         explicit Server() {}
         Server(Server&&) = default;
@@ -1510,8 +1513,8 @@
                     };
                 } break;
                 case SocketType::UNIX_BOOTSTRAP: {
-                    base::unique_fd bootstrapFdClient, bootstrapFdServer;
-                    if (!base::Socketpair(SOCK_STREAM, &bootstrapFdClient, &bootstrapFdServer)) {
+                    unique_fd bootstrapFdClient, bootstrapFdServer;
+                    if (!binder::Socketpair(SOCK_STREAM, &bootstrapFdClient, &bootstrapFdServer)) {
                         return AssertionFailure() << "Socketpair() failed";
                     }
                     auto status = rpcServer->setupUnixDomainSocketBootstrapServer(
@@ -1554,7 +1557,7 @@
                     mConnectToServer = [port] {
                         const char* addr = kLocalInetAddress;
                         auto aiStart = InetSocketAddress::getAddrInfo(addr, port);
-                        if (aiStart == nullptr) return base::unique_fd{};
+                        if (aiStart == nullptr) return unique_fd{};
                         for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
                             auto fd = connectTo(
                                     InetSocketAddress(ai->ai_addr, ai->ai_addrlen, addr, port));
@@ -1562,7 +1565,7 @@
                         }
                         ALOGE("None of the socket address resolved for %s:%u can be connected",
                               addr, port);
-                        return base::unique_fd{};
+                        return unique_fd{};
                     };
                 } break;
                 case SocketType::TIPC: {
@@ -1586,13 +1589,13 @@
             mThread = std::make_unique<std::thread>(&Server::run, this);
         }
 
-        base::unique_fd acceptServerConnection() {
-            return base::unique_fd(TEMP_FAILURE_RETRY(
+        unique_fd acceptServerConnection() {
+            return unique_fd(TEMP_FAILURE_RETRY(
                     accept4(mFd.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK)));
         }
 
-        base::unique_fd recvmsgServerConnection() {
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+        unique_fd recvmsgServerConnection() {
+            std::vector<std::variant<unique_fd, borrowed_fd>> fds;
             int buf;
             iovec iov{&buf, sizeof(buf)};
 
@@ -1601,7 +1604,7 @@
             }
             LOG_ALWAYS_FATAL_IF(fds.size() != 1, "Expected one FD from receiveMessage(), got %zu",
                                 fds.size());
-            return std::move(std::get<base::unique_fd>(fds[0]));
+            return std::move(std::get<unique_fd>(fds[0]));
         }
 
         void run() {
@@ -1609,13 +1612,13 @@
 
             std::vector<std::thread> threads;
             while (OK == mFdTrigger->triggerablePoll(mFd, POLLIN)) {
-                base::unique_fd acceptedFd = mAcceptConnection(this);
+                unique_fd acceptedFd = mAcceptConnection(this);
                 threads.emplace_back(&Server::handleOne, this, std::move(acceptedFd));
             }
 
             for (auto& thread : threads) thread.join();
         }
-        void handleOne(android::base::unique_fd acceptedFd) {
+        void handleOne(unique_fd acceptedFd) {
             ASSERT_TRUE(acceptedFd.ok());
             RpcTransportFd transportFd(std::move(acceptedFd));
             auto serverTransport = mCtx->newTransport(std::move(transportFd), mFdTrigger.get());
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index b2b63e4..a55edd2 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -41,7 +41,6 @@
 #endif
 
 #ifndef __TRUSTY__
-#include <android-base/file.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_libbinder.h>
 #include <binder/ProcessState.h>
@@ -58,7 +57,9 @@
 
 #include "../BuildFlags.h"
 #include "../FdTrigger.h"
+#include "../FdUtils.h"
 #include "../RpcState.h" // for debugging
+#include "FileUtils.h"
 #include "format.h"
 #include "utils/Errors.h"
 
@@ -156,28 +157,28 @@
 };
 
 #ifndef __TRUSTY__
-static inline void writeString(android::base::borrowed_fd fd, std::string_view str) {
+static inline void writeString(binder::borrowed_fd fd, std::string_view str) {
     uint64_t length = str.length();
-    LOG_ALWAYS_FATAL_IF(!android::base::WriteFully(fd, &length, sizeof(length)));
-    LOG_ALWAYS_FATAL_IF(!android::base::WriteFully(fd, str.data(), str.length()));
+    LOG_ALWAYS_FATAL_IF(!android::binder::WriteFully(fd, &length, sizeof(length)));
+    LOG_ALWAYS_FATAL_IF(!android::binder::WriteFully(fd, str.data(), str.length()));
 }
 
-static inline std::string readString(android::base::borrowed_fd fd) {
+static inline std::string readString(binder::borrowed_fd fd) {
     uint64_t length;
-    LOG_ALWAYS_FATAL_IF(!android::base::ReadFully(fd, &length, sizeof(length)));
+    LOG_ALWAYS_FATAL_IF(!android::binder::ReadFully(fd, &length, sizeof(length)));
     std::string ret(length, '\0');
-    LOG_ALWAYS_FATAL_IF(!android::base::ReadFully(fd, ret.data(), length));
+    LOG_ALWAYS_FATAL_IF(!android::binder::ReadFully(fd, ret.data(), length));
     return ret;
 }
 
-static inline void writeToFd(android::base::borrowed_fd fd, const Parcelable& parcelable) {
+static inline void writeToFd(binder::borrowed_fd fd, const Parcelable& parcelable) {
     Parcel parcel;
     LOG_ALWAYS_FATAL_IF(OK != parcelable.writeToParcel(&parcel));
     writeString(fd, std::string(reinterpret_cast<const char*>(parcel.data()), parcel.dataSize()));
 }
 
 template <typename T>
-static inline T readFromFd(android::base::borrowed_fd fd) {
+static inline T readFromFd(binder::borrowed_fd fd) {
     std::string data = readString(fd);
     Parcel parcel;
     LOG_ALWAYS_FATAL_IF(OK !=
@@ -208,12 +209,12 @@
 }
 
 // Create an FD that returns `contents` when read.
-static inline base::unique_fd mockFileDescriptor(std::string contents) {
-    android::base::unique_fd readFd, writeFd;
-    LOG_ALWAYS_FATAL_IF(!android::base::Pipe(&readFd, &writeFd), "%s", strerror(errno));
+static inline binder::unique_fd mockFileDescriptor(std::string contents) {
+    binder::unique_fd readFd, writeFd;
+    LOG_ALWAYS_FATAL_IF(!binder::Pipe(&readFd, &writeFd), "%s", strerror(errno));
     RpcMaybeThread([writeFd = std::move(writeFd), contents = std::move(contents)]() {
         signal(SIGPIPE, SIG_IGN); // ignore possible SIGPIPE from the write
-        if (!WriteStringToFd(contents, writeFd)) {
+        if (!android::binder::WriteStringToFd(contents, writeFd)) {
             int savedErrno = errno;
             LOG_ALWAYS_FATAL_IF(EPIPE != savedErrno, "mockFileDescriptor write failed: %s",
                                 strerror(savedErrno));
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index 5025bd6..28125f1 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -17,6 +17,8 @@
 #include "binderRpcTestCommon.h"
 
 using namespace android;
+using android::binder::ReadFdToString;
+using android::binder::unique_fd;
 
 class MyBinderRpcTestAndroid : public MyBinderRpcTestBase {
 public:
@@ -65,17 +67,17 @@
         std::string acc;
         for (const auto& file : files) {
             std::string result;
-            LOG_ALWAYS_FATAL_IF(!android::base::ReadFdToString(file.get(), &result));
+            LOG_ALWAYS_FATAL_IF(!ReadFdToString(file.get(), &result));
             acc.append(result);
         }
         out->reset(mockFileDescriptor(acc));
         return Status::ok();
     }
 
-    HandoffChannel<android::base::unique_fd> mFdChannel;
+    HandoffChannel<unique_fd> mFdChannel;
 
     Status blockingSendFdOneway(const android::os::ParcelFileDescriptor& fd) override {
-        mFdChannel.write(android::base::unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
+        mFdChannel.write(unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
         return Status::ok();
     }
 
@@ -101,8 +103,8 @@
     __android_log_set_logger(__android_log_stderr_logger);
 
     LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
-    base::unique_fd writeEnd(atoi(argv[1]));
-    base::unique_fd readEnd(atoi(argv[2]));
+    unique_fd writeEnd(atoi(argv[1]));
+    unique_fd readEnd(atoi(argv[2]));
 
     auto serverConfig = readFromFd<BinderRpcTestServerConfig>(readEnd);
     auto socketType = static_cast<SocketType>(serverConfig.socketType);
@@ -123,7 +125,7 @@
     server->setSupportedFileDescriptorTransportModes(serverSupportedFileDescriptorTransportModes);
 
     unsigned int outPort = 0;
-    base::unique_fd socketFd(serverConfig.socketFd);
+    unique_fd socketFd(serverConfig.socketFd);
 
     switch (socketType) {
         case SocketType::PRECONNECTED:
diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp
index 8acaae6..18751cc 100644
--- a/libs/binder/tests/binderRpcTestTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -22,6 +22,8 @@
 
 #include "binderRpcTestFixture.h"
 
+using android::binder::unique_fd;
+
 namespace android {
 
 // Destructors need to be defined, even if pure virtual
@@ -74,7 +76,7 @@
             auto port = trustyIpcPort(serverVersion);
             int rc = connect(port.c_str(), IPC_CONNECT_WAIT_FOR_PORT);
             LOG_ALWAYS_FATAL_IF(rc < 0, "Failed to connect to service: %d", rc);
-            return base::unique_fd(rc);
+            return unique_fd(rc);
         });
         if (options.allowConnectFailure && status != OK) {
             ret->sessions.clear();
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index cbbbe74..41cb552 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -41,6 +41,7 @@
 #include <sys/prctl.h>
 
 using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
+using android::binder::unique_fd;
 
 namespace android {
 namespace tests {
@@ -685,7 +686,7 @@
 
 TEST_F(SafeInterfaceTest, TestIncrementNativeHandle) {
     // Create an fd we can use to send and receive from the remote process
-    base::unique_fd eventFd{eventfd(0 /*initval*/, 0 /*flags*/)};
+    unique_fd eventFd{eventfd(0 /*initval*/, 0 /*flags*/)};
     ASSERT_NE(-1, eventFd);
 
     // Determine the maximum number of fds this process can have open
diff --git a/libs/binder/tests/binderTextOutputTest.cpp b/libs/binder/tests/binderTextOutputTest.cpp
index b37030e..a648c08 100644
--- a/libs/binder/tests/binderTextOutputTest.cpp
+++ b/libs/binder/tests/binderTextOutputTest.cpp
@@ -20,13 +20,14 @@
 #include <limits>
 #include <cstddef>
 
-#include "android-base/file.h"
 #include "android-base/test_utils.h"
 #include <gtest/gtest.h>
 
 #include <binder/Parcel.h>
 #include <binder/TextOutput.h>
 
+#include "../file.h"
+
 static void CheckMessage(CapturedStderr& cap,
                          const char* expected,
                          bool singleline) {
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index ffeca2d..08fe071 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -29,8 +29,9 @@
 
 #include "../../Utils.h"
 
-using ::android::status_t;
 using ::android::HexString;
+using ::android::status_t;
+using ::android::binder::unique_fd;
 
 enum ByteEnum : int8_t {};
 enum IntEnum : int32_t {};
@@ -307,11 +308,12 @@
     },
     PARCEL_READ_NO_STATUS(int, readFileDescriptor),
     PARCEL_READ_NO_STATUS(int, readParcelFileDescriptor),
-    PARCEL_READ_WITH_STATUS(android::base::unique_fd, readUniqueFileDescriptor),
+    PARCEL_READ_WITH_STATUS(unique_fd, readUniqueFileDescriptor),
 
-    PARCEL_READ_WITH_STATUS(std::unique_ptr<std::vector<android::base::unique_fd>>, readUniqueFileDescriptorVector),
-    PARCEL_READ_WITH_STATUS(std::optional<std::vector<android::base::unique_fd>>, readUniqueFileDescriptorVector),
-    PARCEL_READ_WITH_STATUS(std::vector<android::base::unique_fd>, readUniqueFileDescriptorVector),
+    PARCEL_READ_WITH_STATUS(std::unique_ptr<std::vector<unique_fd>>,
+            readUniqueFileDescriptorVector),
+    PARCEL_READ_WITH_STATUS(std::optional<std::vector<unique_fd>>, readUniqueFileDescriptorVector),
+    PARCEL_READ_WITH_STATUS(std::vector<unique_fd>, readUniqueFileDescriptorVector),
 
     [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
         size_t len = provider.ConsumeIntegral<size_t>();
diff --git a/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp b/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp
index c0fdaea..57521f4 100644
--- a/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
+#include "../../FileUtils.h"
+
 #include <android-base/logging.h>
-#include <android-base/unique_fd.h>
 #include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
 
 #include <fuzzseeds/random_parcel_seeds.h>
 
 #include <sys/prctl.h>
+#include <sys/stat.h>
 
 using android::generateSeedsFromRecording;
 using android::status_t;
-using android::base::unique_fd;
+using android::binder::unique_fd;
 using android::binder::debug::RecordedTransaction;
 
 status_t generateCorpus(const char* recordingPath, const char* corpusDir) {
@@ -49,7 +51,7 @@
         std::string filePath = std::string(corpusDir) + std::string("transaction_") +
                 std::to_string(transactionNumber);
         constexpr int openFlags = O_WRONLY | O_CREAT | O_BINARY | O_CLOEXEC;
-        android::base::unique_fd corpusFd(open(filePath.c_str(), openFlags, 0666));
+        unique_fd corpusFd(open(filePath.c_str(), openFlags, 0666));
         if (!corpusFd.ok()) {
             std::cerr << "Failed to open fd. Path " << filePath
                       << " with error: " << strerror(errno) << std::endl;
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
index 6ea9708..8d1299d 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
 #include <vector>
@@ -27,6 +27,6 @@
 // get a random FD for use in fuzzing, of a few different specific types
 //
 // may return multiple FDs (e.g. pipe), but will always return at least one
-std::vector<base::unique_fd> getRandomFds(FuzzedDataProvider* provider);
+std::vector<binder::unique_fd> getRandomFds(FuzzedDataProvider* provider);
 
 } // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
index 27587a9..2812da7 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
@@ -27,7 +27,7 @@
 struct RandomParcelOptions {
     std::function<void(Parcel* p, FuzzedDataProvider& provider)> writeHeader;
     std::vector<sp<IBinder>> extraBinders;
-    std::vector<base::unique_fd> extraFds;
+    std::vector<binder::unique_fd> extraFds;
 };
 
 /**
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 071250d..694b68d 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
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
+#include "../../../../file.h"
+
 #include <android-base/logging.h>
 
 #include <binder/Binder.h>
@@ -40,6 +41,6 @@
 template <typename T>
 void writeReversedBuffer(std::vector<std::byte>& integralBuffer, T val);
 } // namespace impl
-void generateSeedsFromRecording(base::borrowed_fd fd,
+void generateSeedsFromRecording(binder::borrowed_fd fd,
                                 const binder::debug::RecordedTransaction& transaction);
 } // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 38e6f32..02e69cc 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -23,6 +23,8 @@
 
 #include <private/android_filesystem_config.h>
 
+using android::binder::unique_fd;
+
 namespace android {
 
 void fuzzService(const sp<IBinder>& binder, FuzzedDataProvider&& provider) {
@@ -103,7 +105,7 @@
                                     retBinders.end());
         auto retFds = reply.debugReadAllFileDescriptors();
         for (size_t i = 0; i < retFds.size(); i++) {
-            options.extraFds.push_back(base::unique_fd(dup(retFds[i])));
+            options.extraFds.push_back(unique_fd(dup(retFds[i])));
         }
     }
 
diff --git a/libs/binder/tests/parcel_fuzzer/random_fd.cpp b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
index 4a9bd07..c7d1533 100644
--- a/libs/binder/tests/parcel_fuzzer/random_fd.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
@@ -23,7 +23,7 @@
 
 namespace android {
 
-using base::unique_fd;
+using binder::unique_fd;
 
 std::vector<unique_fd> getRandomFds(FuzzedDataProvider* provider) {
     const char* fdType;
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index f367b41..4e58dc4 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -23,6 +23,8 @@
 #include <fuzzbinder/random_fd.h>
 #include <utils/String16.h>
 
+using android::binder::unique_fd;
+
 namespace android {
 
 static void fillRandomParcelData(Parcel* p, FuzzedDataProvider&& provider) {
@@ -72,7 +74,7 @@
                     }
 
                     if (options->extraFds.size() > 0 && provider.ConsumeBool()) {
-                        const base::unique_fd& fd = options->extraFds.at(
+                        const unique_fd& fd = options->extraFds.at(
                                 provider.ConsumeIntegralInRange<size_t>(0,
                                                                         options->extraFds.size() -
                                                                                 1));
@@ -83,7 +85,7 @@
                             return;
                         }
 
-                        std::vector<base::unique_fd> fds = getRandomFds(&provider);
+                        std::vector<unique_fd> fds = getRandomFds(&provider);
                         CHECK(OK ==
                               p->writeFileDescriptor(fds.begin()->release(),
                                                      true /*takeOwnership*/));
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
index 9e3e2ab..7b3c806 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
 #include <android-base/logging.h>
 
 #include <binder/RecordedTransaction.h>
 
 #include <fuzzseeds/random_parcel_seeds.h>
 
-using android::base::WriteFully;
+#include "../../file.h"
+
+using android::binder::borrowed_fd;
+using android::binder::WriteFully;
 
 namespace android {
 namespace impl {
@@ -64,7 +66,7 @@
 
 } // namespace impl
 
-void generateSeedsFromRecording(base::borrowed_fd fd,
+void generateSeedsFromRecording(borrowed_fd fd,
                                 const binder::debug::RecordedTransaction& transaction) {
     // Write Reserved bytes for future use
     std::vector<uint8_t> reservedBytes(8);
diff --git a/libs/binder/tests/rpc_fuzzer/Android.bp b/libs/binder/tests/rpc_fuzzer/Android.bp
index 71e847f..ab72bfd 100644
--- a/libs/binder/tests/rpc_fuzzer/Android.bp
+++ b/libs/binder/tests/rpc_fuzzer/Android.bp
@@ -25,13 +25,14 @@
         "libbase",
         "libcutils",
         "liblog",
+        "libbinder_test_utils",
         "libbinder_tls_static",
         "libbinder_tls_test_utils",
         "libssl_fuzz_unsafe",
         "libcrypto_fuzz_unsafe",
     ],
     cflags: [
-        "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE" // for RAND_reset_for_fuzzing
+        "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE", // for RAND_reset_for_fuzzing
     ],
     target: {
         android: {
diff --git a/libs/binder/tests/rpc_fuzzer/main.cpp b/libs/binder/tests/rpc_fuzzer/main.cpp
index dcc8b8e..50fc2f2 100644
--- a/libs/binder/tests/rpc_fuzzer/main.cpp
+++ b/libs/binder/tests/rpc_fuzzer/main.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
+#include "../FileUtils.h"
+
 #include <android-base/logging.h>
-#include <android-base/unique_fd.h>
 #include <binder/Binder.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
@@ -24,13 +24,18 @@
 #include <binder/RpcTransport.h>
 #include <binder/RpcTransportRaw.h>
 #include <binder/RpcTransportTls.h>
+#include <binder/unique_fd.h>
 #include <fuzzer/FuzzedDataProvider.h>
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 
 #include <sys/resource.h>
+#include <sys/socket.h>
 #include <sys/un.h>
 
+using android::binder::GetExecutableDirectory;
+using android::binder::unique_fd;
+
 namespace android {
 
 static const std::string kSock = std::string(getenv("TMPDIR") ?: "/tmp") +
@@ -76,12 +81,12 @@
 ServerAuth readServerKeyAndCert() {
     ServerAuth ret;
 
-    auto keyPath = android::base::GetExecutableDirectory() + "/data/server.key";
+    auto keyPath = GetExecutableDirectory() + "/data/server.key";
     bssl::UniquePtr<BIO> keyBio(BIO_new_file(keyPath.c_str(), "r"));
     ret.pkey.reset(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, passwordCallback, nullptr));
     CHECK_NE(ret.pkey.get(), nullptr);
 
-    auto certPath = android::base::GetExecutableDirectory() + "/data/server.crt";
+    auto certPath = GetExecutableDirectory() + "/data/server.crt";
     bssl::UniquePtr<BIO> certBio(BIO_new_file(certPath.c_str(), "r"));
     ret.cert.reset(PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr));
     CHECK_NE(ret.cert.get(), nullptr);
@@ -129,7 +134,7 @@
     CHECK_LT(kSock.size(), sizeof(addr.sun_path));
     memcpy(&addr.sun_path, kSock.c_str(), kSock.size());
 
-    std::vector<base::unique_fd> connections;
+    std::vector<unique_fd> connections;
 
     bool hangupBeforeShutdown = provider.ConsumeBool();
 
@@ -140,7 +145,7 @@
     while (provider.remaining_bytes() > 0) {
         if (connections.empty() ||
             (connections.size() < kMaxConnections && provider.ConsumeBool())) {
-            base::unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+            unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
             CHECK_NE(fd.get(), -1);
             CHECK_EQ(0,
                      TEMP_FAILURE_RETRY(
diff --git a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
index 8d2b714..993418a 100644
--- a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
@@ -74,7 +74,7 @@
                                   bbinder->getDebugPid();
                               },
                               [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
-                                  (void)bbinder->setRpcClientDebug(android::base::unique_fd(),
+                                  (void)bbinder->setRpcClientDebug(binder::unique_fd(),
                                                                    sp<BBinder>::make());
                               }};
 
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
index 0706182..87b0fb6 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
@@ -19,13 +19,15 @@
 
 #include "fuzzer/FuzzedDataProvider.h"
 
+using android::binder::unique_fd;
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     std::FILE* intermediateFile = std::tmpfile();
     fwrite(data, sizeof(uint8_t), size, intermediateFile);
     rewind(intermediateFile);
     int fileNumber = fileno(intermediateFile);
 
-    android::base::unique_fd fd(dup(fileNumber));
+    unique_fd fd(dup(fileNumber));
 
     auto transaction = android::binder::debug::RecordedTransaction::fromFile(fd);
 
@@ -34,7 +36,7 @@
     if (transaction.has_value()) {
         intermediateFile = std::tmpfile();
 
-        android::base::unique_fd fdForWriting(dup(fileno(intermediateFile)));
+        unique_fd fdForWriting(dup(fileno(intermediateFile)));
         auto writeStatus [[maybe_unused]] = transaction.value().dumpToFile(fdForWriting);
 
         std::fclose(intermediateFile);
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
index 9289f6a..fa939e6 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
@@ -22,6 +22,7 @@
 #include "fuzzer/FuzzedDataProvider.h"
 
 using android::fillRandomParcel;
+using android::binder::unique_fd;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     FuzzedDataProvider provider = FuzzedDataProvider(data, size);
@@ -53,7 +54,7 @@
 
     if (transaction.has_value()) {
         std::FILE* intermediateFile = std::tmpfile();
-        android::base::unique_fd fdForWriting(dup(fileno(intermediateFile)));
+        unique_fd fdForWriting(dup(fileno(intermediateFile)));
         auto writeStatus [[maybe_unused]] = transaction.value().dumpToFile(fdForWriting);
 
         std::fclose(intermediateFile);
diff --git a/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
index 5e3502a..fe09978 100644
--- a/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
+#include "../../file.h"
+
 #include <fuzzer/FuzzedDataProvider.h>
 
 #include <binder/Parcel.h>
 #include <binder/TextOutput.h>
-#include "android-base/file.h"
 #include "android-base/test_utils.h"
 
 #include <fcntl.h>
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 0d18b0b..ca14286 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -26,9 +26,12 @@
 #include "../OS.h"
 #include "TrustyStatus.h"
 
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
+
 namespace android::binder::os {
 
-status_t setNonBlocking(android::base::borrowed_fd /*fd*/) {
+status_t setNonBlocking(borrowed_fd /*fd*/) {
     // Trusty IPC syscalls are all non-blocking by default.
     return OK;
 }
@@ -59,14 +62,14 @@
 
 ssize_t sendMessageOnSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
+        const std::vector<std::variant<unique_fd, borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
     return -1;
 }
 
 ssize_t receiveMessageFromSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
+        std::vector<std::variant<unique_fd, borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
     return -1;
 }
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 8f64323..0872b90 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -28,6 +28,7 @@
 #include "TrustyStatus.h"
 
 using android::base::unexpected;
+using android::binder::unique_fd;
 
 namespace android {
 
@@ -129,7 +130,7 @@
     if (chanDup < 0) {
         return chanDup;
     }
-    base::unique_fd clientFd(chanDup);
+    unique_fd clientFd(chanDup);
     android::RpcTransportFd transportFd(std::move(clientFd));
 
     std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
diff --git a/libs/binder/trusty/RpcTransportTipcTrusty.cpp b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
index 6bb45e2..c74ba0a 100644
--- a/libs/binder/trusty/RpcTransportTipcTrusty.cpp
+++ b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
@@ -30,6 +30,8 @@
 namespace android {
 
 using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 // RpcTransport for Trusty.
 class RpcTransportTipcTrusty : public RpcTransport {
@@ -48,8 +50,7 @@
     status_t interruptableWriteFully(
             FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         if (niovs < 0) {
             return BAD_VALUE;
         }
@@ -118,7 +119,7 @@
     status_t interruptableReadFully(
             FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+            std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         if (niovs < 0) {
             return BAD_VALUE;
         }
@@ -170,7 +171,7 @@
                 if (ancillaryFds != nullptr) {
                     ancillaryFds->reserve(ancillaryFds->size() + mMessageInfo.num_handles);
                     for (size_t i = 0; i < mMessageInfo.num_handles; i++) {
-                        ancillaryFds->emplace_back(base::unique_fd(msgHandles[i]));
+                        ancillaryFds->emplace_back(unique_fd(msgHandles[i]));
                     }
 
                     // Clear the saved number of handles so we don't accidentally
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index aa476f9..7382a30 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -17,11 +17,11 @@
 #pragma once
 
 #include <android-base/expected.h>
-#include <android-base/unique_fd.h>
 #include <binder/IBinder.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 9cad556..dbddbe1 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -43,6 +43,7 @@
 	$(LIBBINDER_DIR)/Stability.cpp \
 	$(LIBBINDER_DIR)/Status.cpp \
 	$(LIBBINDER_DIR)/Utils.cpp \
+	$(LIBBINDER_DIR)/file.cpp \
 	$(LIBBASE_DIR)/hex.cpp \
 	$(LIBBASE_DIR)/stringprintf.cpp \
 	$(LIBUTILS_DIR)/binder/Errors.cpp \
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 30cedb0..09e98d0 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -446,7 +446,7 @@
     msg->getSanitizedCopy(&cleanMsg);
     ssize_t nWrite;
     do {
-        nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+        nWrite = ::send(getFd().get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
     } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite < 0) {
@@ -478,7 +478,7 @@
 status_t InputChannel::receiveMessage(InputMessage* msg) {
     ssize_t nRead;
     do {
-        nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+        nRead = ::recv(getFd().get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
     } while (nRead == -1 && errno == EINTR);
 
     if (nRead < 0) {
@@ -551,7 +551,7 @@
 }
 
 base::unique_fd InputChannel::dupFd() const {
-    android::base::unique_fd newFd(::dup(getFd()));
+    base::unique_fd newFd(::dup(getFd().get()));
     if (!newFd.ok()) {
         ALOGE("Could not duplicate fd %i for channel %s: %s", getFd().get(), getName().c_str(),
               strerror(errno));
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 5f05a0f..b60d7c9 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -124,10 +124,7 @@
                         self.name, device_id, self.touching_pointer_ids_by_device
                     ));
                 }
-                let it = self
-                    .touching_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
+                let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
                 it.insert(pointer_properties[0].id);
             }
             MotionAction::PointerDown { action_index } => {
@@ -224,19 +221,13 @@
                         self.name, device_id, self.hovering_pointer_ids_by_device
                     ));
                 }
-                let it = self
-                    .hovering_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
+                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
                 it.insert(pointer_properties[0].id);
             }
             MotionAction::HoverMove => {
                 // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
                 // If there was no prior HOVER_ENTER, just start a new hovering pointer.
-                let it = self
-                    .hovering_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
+                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
                 it.insert(pointer_properties[0].id);
             }
             MotionAction::HoverExit => {
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 8821c0e..ba20d1f 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -433,6 +433,10 @@
     // Looks like this would slow things down and we can't depend on it on all platforms
     interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
 
+    if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
+        BAIL("Protected memory not supported");
+    }
+
     float queuePriorities[1] = {0.0f};
     void* queueNextPtr = nullptr;
 
diff --git a/libs/ui/include/ui/DisplayMap.h b/libs/ui/include/ui/DisplayMap.h
index 7eacb0a..65d2b8f 100644
--- a/libs/ui/include/ui/DisplayMap.h
+++ b/libs/ui/include/ui/DisplayMap.h
@@ -23,13 +23,18 @@
 
 // The static capacities were chosen to exceed a typical number of physical and/or virtual displays.
 
+constexpr size_t kDisplayCapacity = 5;
 template <typename Key, typename Value>
-using DisplayMap = ftl::SmallMap<Key, Value, 5>;
+using DisplayMap = ftl::SmallMap<Key, Value, kDisplayCapacity>;
 
+constexpr size_t kPhysicalDisplayCapacity = 3;
 template <typename Key, typename Value>
-using PhysicalDisplayMap = ftl::SmallMap<Key, Value, 3>;
+using PhysicalDisplayMap = ftl::SmallMap<Key, Value, kPhysicalDisplayCapacity>;
 
 template <typename T>
-using PhysicalDisplayVector = ftl::SmallVector<T, 3>;
+using DisplayVector = ftl::SmallVector<T, kDisplayCapacity>;
+
+template <typename T>
+using PhysicalDisplayVector = ftl::SmallVector<T, kPhysicalDisplayCapacity>;
 
 } // namespace android::ui
diff --git a/libs/vibrator/fuzzer/Android.bp b/libs/vibrator/fuzzer/Android.bp
index f2a313c..cb063af 100644
--- a/libs/vibrator/fuzzer/Android.bp
+++ b/libs/vibrator/fuzzer/Android.bp
@@ -47,6 +47,17 @@
     ],
 
     fuzz_config: {
-        componentid: 155276,
+        cc: [
+            "android-haptics@google.com",
+        ],
+        componentid: 345036,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libvibrator",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
     },
 }
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index e529bdd..f1faf69 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -31,6 +31,11 @@
             args.pointerProperties[0].toolType == ToolType::MOUSE;
 }
 
+bool isFromTouchpad(const NotifyMotionArgs& args) {
+    return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+            args.pointerProperties[0].toolType == ToolType::FINGER;
+}
+
 bool isHoverAction(int32_t action) {
     return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
             action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
@@ -83,6 +88,8 @@
 
     if (isFromMouse(args)) {
         return processMouseEventLocked(args);
+    } else if (isFromTouchpad(args)) {
+        return processTouchpadEventLocked(args);
     } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
         processStylusHoverEventLocked(args);
     } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
@@ -97,17 +104,7 @@
                    << args.dump();
     }
 
-    const int32_t displayId = getTargetMouseDisplayLocked(args.displayId);
-
-    // Get the mouse pointer controller for the display, or create one if it doesn't exist.
-    auto [it, emplaced] =
-            mMousePointersByDisplay.try_emplace(displayId,
-                                                getMouseControllerConstructor(displayId));
-    if (emplaced) {
-        notifyPointerDisplayIdChangedLocked();
-    }
-
-    PointerControllerInterface& pc = *it->second;
+    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
 
     const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
     const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
@@ -124,6 +121,40 @@
     return newArgs;
 }
 
+NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+
+    NotifyMotionArgs newArgs(args);
+    newArgs.displayId = displayId;
+    if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
+        // This is a movement of the mouse pointer.
+        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        pc.move(deltaX, deltaY);
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+        const auto [x, y] = pc.getPosition();
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    } else {
+        // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+        const auto [x, y] = pc.getPosition();
+        for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
+            newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                  args.pointerCoords[i].getX() + x);
+            newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                  args.pointerCoords[i].getY() + y);
+        }
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    }
+    return newArgs;
+}
+
 /**
  * When screen is touched, fade the mouse pointer on that display. We only call fade for
  * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
@@ -270,6 +301,21 @@
     return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
 }
 
+std::pair<int32_t, PointerControllerInterface&>
+PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) {
+    const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
+
+    // Get the mouse pointer controller for the display, or create one if it doesn't exist.
+    auto [it, emplaced] =
+            mMousePointersByDisplay.try_emplace(displayId,
+                                                getMouseControllerConstructor(displayId));
+    if (emplaced) {
+        notifyPointerDisplayIdChangedLocked();
+    }
+
+    return {displayId, *it->second};
+}
+
 InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
     auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
                            [deviceId](const auto& info) { return info.getId() == deviceId; });
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 26d2fef..90a0d3f 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -95,10 +95,13 @@
     void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
     const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
     int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+    std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
+            int32_t associatedDisplayId) REQUIRES(mLock);
     InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
 
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processDeviceReset(const NotifyDeviceResetArgs& args);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 7f4de7a..6ad3de0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2305,6 +2305,13 @@
     }
 
     if (isHoverAction) {
+        if (wasDown) {
+            // 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 {};
+        }
         // 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(entry.deviceId);
@@ -2687,15 +2694,7 @@
     }
 
     // Update final pieces of touch state if the injector had permission.
-    if (isHoverAction) {
-        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) {
+    if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         // Pointer went up.
         tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id);
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
@@ -3981,16 +3980,6 @@
 
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
         const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
-    if ((options.mode == CancelationOptions::Mode::CANCEL_POINTER_EVENTS ||
-         options.mode == CancelationOptions::Mode::CANCEL_ALL_EVENTS) &&
-        mDragState && mDragState->dragWindow->getToken() == connection->inputChannel->getToken()) {
-        LOG(INFO) << __func__
-                  << ": Canceling drag and drop because the pointers for the drag window are being "
-                     "canceled.";
-        sendDropWindowCommandLocked(nullptr, /*x=*/0, /*y=*/0);
-        mDragState.reset();
-    }
-
     if (connection->status == Connection::Status::BROKEN) {
         return;
     }
@@ -4003,6 +3992,7 @@
     if (cancelationEvents.empty()) {
         return;
     }
+
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
               "with reality: %s, mode=%s.",
@@ -4051,6 +4041,14 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
+                    if (mDragState && mDragState->dragWindow->getToken() == token &&
+                        pointerIds.test(mDragState->pointerId)) {
+                        LOG(INFO) << __func__
+                                  << ": Canceling drag and drop because the pointers for the drag "
+                                     "window are being canceled.";
+                        sendDropWindowCommandLocked(nullptr, /*x=*/0, /*y=*/0);
+                        mDragState.reset();
+                    }
                     addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
                                                  pointerIds, motionEntry.downTime, targets);
                 } else {
@@ -5888,7 +5886,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        int fd = serverChannel->getFd();
+        auto&& fd = serverChannel->getFd();
         std::shared_ptr<Connection> connection =
                 std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
                                              mIdGenerator);
@@ -5901,7 +5899,7 @@
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     } // release lock
 
@@ -5931,7 +5929,7 @@
         std::shared_ptr<Connection> connection =
                 std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        const int fd = serverChannel->getFd();
+        auto&& fd = serverChannel->getFd();
 
         if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5942,7 +5940,7 @@
 
         mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
 
-        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     }
 
@@ -5981,7 +5979,7 @@
         removeMonitorChannelLocked(connectionToken);
     }
 
-    mLooper->removeFd(connection->inputChannel->getFd());
+    mLooper->removeFd(connection->inputChannel->getFd().get());
 
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 5367751..cd0500c 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -128,20 +128,14 @@
 
 std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
     std::set<DeviceId> deviceIds;
-    for (const auto& [deviceId, _] : mDeviceStates) {
-        deviceIds.insert(deviceId);
+    for (const auto& [deviceId, deviceState] : mDeviceStates) {
+        if (deviceState.touchingPointerIds.any()) {
+            deviceIds.insert(deviceId);
+        }
     }
     return deviceIds;
 }
 
-std::set<DeviceId> TouchedWindow::getActiveDeviceIds() const {
-    std::set<DeviceId> out;
-    for (const auto& [deviceId, _] : mDeviceStates) {
-        out.emplace(deviceId);
-    }
-    return out;
-}
-
 bool TouchedWindow::hasPilferingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 6d2283e..9a31678 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -48,15 +48,7 @@
     void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
-    /**
-     * Get the currently active touching device id. If there isn't exactly 1 touching device, return
-     * nullopt.
-     */
     std::set<DeviceId> getTouchingDeviceIds() const;
-    /**
-     * The ids of devices that are currently touching or hovering.
-     */
-    std::set<DeviceId> getActiveDeviceIds() const;
 
     // Pilfering pointers
     bool hasPilferingPointers(DeviceId deviceId) const;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 7aeb215..58e35a6 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -527,10 +527,8 @@
         if (mEnablePointerChoreographer) {
             // Always use DISPLAY_ID_NONE for mouse events.
             // PointerChoreographer will make it target the correct the displayId later.
-            const auto pointerViewport =
-                    getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
-            mDisplayId = pointerViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
-            resolvedViewport = pointerViewport;
+            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+            mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
         } else {
             mDisplayId = mPointerController->getDisplayId();
             if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index c76fec7..34ca0b3 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -246,7 +246,8 @@
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
-        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
+        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())),
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
     RawAbsoluteAxisInfo slotAxisInfo;
     deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
     if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
@@ -331,31 +332,56 @@
 
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         mDisplayId = ADISPLAY_ID_NONE;
-        if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+        std::optional<DisplayViewport> resolvedViewport;
+        std::optional<FloatRect> boundsInLogicalDisplay;
+        if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
             // This InputDevice is associated with a viewport.
             // Only generate events for the associated display.
-            const bool mismatchedPointerDisplay =
-                    (viewport->displayId != mPointerController->getDisplayId());
-            if (mismatchedPointerDisplay) {
-                ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
-                      "controller",
-                      mDeviceContext.getName().c_str());
+            mDisplayId = assocViewport->displayId;
+            resolvedViewport = *assocViewport;
+            if (!mEnablePointerChoreographer) {
+                const bool mismatchedPointerDisplay =
+                        (assocViewport->displayId != mPointerController->getDisplayId());
+                if (mismatchedPointerDisplay) {
+                    ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
+                          "controller",
+                          mDeviceContext.getName().c_str());
+                    mDisplayId.reset();
+                }
             }
-            mDisplayId = mismatchedPointerDisplay ? std::nullopt
-                                                  : std::make_optional(viewport->displayId);
         } else {
             // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-            mDisplayId = mPointerController->getDisplayId();
-        }
-
-        ui::Rotation orientation = ui::ROTATION_0;
-        if (mDisplayId.has_value()) {
-            if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
-                orientation = getInverseRotation(viewport->orientation);
+            if (mEnablePointerChoreographer) {
+                // Always use DISPLAY_ID_NONE for touchpad events.
+                // PointerChoreographer will make it target the correct the displayId later.
+                resolvedViewport =
+                        getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+                mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+            } else {
+                mDisplayId = mPointerController->getDisplayId();
+                if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+                    resolvedViewport = *v;
+                }
+                if (auto bounds = mPointerController->getBounds(); bounds) {
+                    boundsInLogicalDisplay = *bounds;
+                }
             }
         }
+
         mGestureConverter.setDisplayId(mDisplayId);
-        mGestureConverter.setOrientation(orientation);
+        mGestureConverter.setOrientation(resolvedViewport
+                                                 ? getInverseRotation(resolvedViewport->orientation)
+                                                 : ui::ROTATION_0);
+
+        if (!boundsInLogicalDisplay) {
+            boundsInLogicalDisplay = resolvedViewport
+                    ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+                                static_cast<float>(resolvedViewport->logicalTop),
+                                static_cast<float>(resolvedViewport->logicalRight - 1),
+                                static_cast<float>(resolvedViewport->logicalBottom - 1)}
+                    : FloatRect{0, 0, 0, 0};
+        }
+        mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay);
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index a68ae43..ece0eca 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -107,6 +107,8 @@
     // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
     std::set<int32_t> mPalmTrackingIds;
 
+    const bool mEnablePointerChoreographer;
+
     // The display that events generated by this mapper should target. This can be set to
     // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 4d2b66d..9552104 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -60,7 +60,8 @@
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mPointerController(readerContext.getPointerController(deviceId)) {
+        mPointerController(readerContext.getPointerController(deviceId)),
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
 }
@@ -110,9 +111,11 @@
 
     // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture
     // is enabled.
-    if (std::optional<FloatRect> rect = mPointerController->getBounds(); rect.has_value()) {
-        info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0);
-        info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0);
+    if (!mBoundsInLogicalDisplay.isEmpty()) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left,
+                            mBoundsInLogicalDisplay.right, 0, 0, 0);
+        info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, mBoundsInLogicalDisplay.top,
+                            mBoundsInLogicalDisplay.bottom, 0, 0, 0);
     }
 
     info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0);
@@ -172,7 +175,8 @@
     mPointerController->move(deltaX, deltaY);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -196,7 +200,8 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -273,7 +278,8 @@
 
 std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -308,7 +314,8 @@
                                                      const Gesture& gesture) {
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
@@ -376,7 +383,8 @@
 }
 
 NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
     NotifyMotionArgs args =
@@ -394,7 +402,8 @@
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
         // three and then put a fourth finger down), the gesture library will treat it as two
@@ -457,7 +466,8 @@
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         return out;
     }
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
 
@@ -481,7 +491,8 @@
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
                                                                   const Gesture& gesture) {
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     // Pinch gesture phases are reported a little differently from others, in that the same details
     // struct is used for all phases of the gesture, just with different zoom_state values. When
@@ -538,7 +549,8 @@
 
 std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
     out.push_back(makeMotionArgs(when, readTime,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index e6cf617..732a4b2 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -48,6 +48,8 @@
 
     void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
 
+    void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; }
+
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
@@ -85,8 +87,10 @@
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
     std::shared_ptr<PointerControllerInterface> mPointerController;
+    const bool mEnablePointerChoreographer;
 
     std::optional<int32_t> mDisplayId;
+    FloatRect mBoundsInLogicalDisplay{};
     ui::Rotation mOrientation = ui::ROTATION_0;
     RawAbsoluteAxisInfo mXAxisInfo;
     RawAbsoluteAxisInfo mYAxisInfo;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 5475594..8043812 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -42,7 +42,10 @@
 }
 
 int32_t FakePointerController::getDisplayId() const {
-    return mDisplayId;
+    if (!mDisplayId) {
+        return ADISPLAY_ID_NONE;
+    }
+    return *mDisplayId;
 }
 
 void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
@@ -51,6 +54,15 @@
               viewport.logicalBottom - 1);
 }
 
+void FakePointerController::assertViewportSet(int32_t displayId) {
+    ASSERT_TRUE(mDisplayId);
+    ASSERT_EQ(displayId, mDisplayId);
+}
+
+void FakePointerController::assertViewportNotSet() {
+    ASSERT_EQ(std::nullopt, mDisplayId);
+}
+
 void FakePointerController::assertPosition(float x, float y) {
     const auto [actualX, actualY] = getPosition();
     ASSERT_NEAR(x, actualX, 1);
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index d7e40b3..9be6a6c 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -36,6 +36,8 @@
     int32_t getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
+    void assertViewportSet(int32_t displayId);
+    void assertViewportNotSet();
     void assertPosition(float x, float y);
     void assertSpotCount(int32_t displayId, int32_t count);
     bool isPointerShown();
@@ -54,7 +56,7 @@
     bool mHaveBounds{false};
     float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
     float mX{0}, mY{0};
-    int32_t mDisplayId{ADISPLAY_ID_NONE};
+    std::optional<int32_t> mDisplayId;
     bool mIsPointerShown{false};
 
     std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 41c7392..d2b68dd 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -35,16 +35,18 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 namespace {
 
 const auto TOUCHPAD_PALM_REJECTION =
-        ACONFIG_FLAG(com::android::input::flags, enable_touchpad_typing_palm_rejection);
+        ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection);
 
 } // namespace
 
 using testing::AllOf;
 
-class GestureConverterTest : public testing::Test {
+class GestureConverterTestBase : public testing::Test {
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
@@ -91,6 +93,14 @@
     std::shared_ptr<FakePointerController> mFakePointerController;
 };
 
+class GestureConverterTest : public GestureConverterTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        GestureConverterTestBase::SetUp();
+    }
+};
+
 TEST_F(GestureConverterTest, Move) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
@@ -1295,4 +1305,1182 @@
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        GestureConverterTestBase::SetUp();
+    }
+};
+
+TEST_F(GestureConverterTestWithChoreographer, Move) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Press left and right buttons at once
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    ASSERT_EQ(3u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Then release the left button
+    Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                          /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Finally release the right button
+    Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+                           /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
+    ASSERT_EQ(3u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Press the button
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+                        /* is_tap= */ false);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Move
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Release the button
+    Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                      /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                      /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
+    ASSERT_EQ(3u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -10),
+                      WithGestureScrollDistance(0, 10, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
+                      WithGestureScrollDistance(0, 5, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-10, 0),
+                      WithGestureScrollDistance(0, 10, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
+                      WithGestureScrollDistance(0, 5, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionClassification(MotionClassification::NONE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like pinch.
+    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/0);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
+                        /*dy=*/10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
+                         /*dy=*/5);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like pinch.
+    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) {
+    // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+    // start swiping up and then start moving left or right, it'll return gesture events with only Y
+    // deltas until you lift your fingers and start swiping again. That's why each of these tests
+    // only checks movement in one dimension.
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                         /* dx= */ 10, /* dy= */ 0);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(5u, args.size());
+
+    // Four fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger3Start = arg.pointerCoords[3];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 5, /* dy= */ 0);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(4u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0),
+                      WithPointerCoords(1, 80, 0), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, 100, 0),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0),
+                      WithPointerCoords(1, 110, 0), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like scroll.
+    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
+                          /*dy=*/0);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(3u, args.size());
+
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(ToolType::FINGER),
+                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Tap) {
+    // Tap should produce button press/release events
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+    ASSERT_EQ(5u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Click) {
+    // Click should produce button press/release events
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                              /* down= */ GESTURES_BUTTON_LEFT,
+                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* down= */ GESTURES_BUTTON_NONE,
+                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+
+    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+    // Click should still produce button press/release events
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                              /* down= */ GESTURES_BUTTON_LEFT,
+                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* down= */ GESTURES_BUTTON_NONE,
+                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+                      WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+    // initially disable tap-to-click
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                      WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 71362e3..e220133 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -20,6 +20,7 @@
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/silent_death_test.h>
 #include <android-base/stringprintf.h>
@@ -53,6 +54,7 @@
 
 using namespace ftl::flag_operators;
 using testing::AllOf;
+using testing::Not;
 
 namespace {
 
@@ -1050,12 +1052,10 @@
         }
         if (event->getType() == InputEventType::KEY) {
             KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
-            ADD_FAILURE() << "Received key event "
-                          << KeyEvent::actionToString(keyEvent.getAction());
+            ADD_FAILURE() << "Received key event " << keyEvent;
         } else if (event->getType() == InputEventType::MOTION) {
             MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
-            ADD_FAILURE() << "Received motion event "
-                          << MotionEvent::actionToString(motionEvent.getAction());
+            ADD_FAILURE() << "Received motion event " << motionEvent;
         } else if (event->getType() == InputEventType::FOCUS) {
             FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
             ADD_FAILURE() << "Received focus event, hasFocus = "
@@ -1319,10 +1319,13 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
-    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+    const MotionEvent& consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
         MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
-        ASSERT_THAT(*motionEvent, matcher);
+        if (nullptr == motionEvent) {
+            LOG(FATAL) << "Did not get a motion event, but expected " << matcher;
+        }
+        EXPECT_THAT(*motionEvent, matcher);
+        return *motionEvent;
     }
 
     void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
@@ -1779,8 +1782,10 @@
     mDispatcher->onWindowInfosChanged(
             {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(200))
+                                        .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
@@ -1788,11 +1793,13 @@
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(110).y(200))
+                                        .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    foregroundWindow->consumeMotionMove();
+    foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
     wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Now the foreground window goes away, but the wallpaper stays
@@ -6539,6 +6546,55 @@
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
 }
 
+TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                                           ADISPLAY_ID_DEFAULT);
+    left->setFrame(Rect(0, 0, 100, 100));
+    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    right->setFrame(Rect(100, 0, 200, 100));
+    sp<FakeWindowHandle> spy =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    spy->setFrame(Rect(0, 0, 200, 100));
+    spy->setTrustedOverlay(true);
+    spy->setSpy(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+    // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId.
+    NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                                     ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+    mDispatcher->notifyMotion(notifyArgs);
+
+    const MotionEvent& leftEnter = left->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)),
+                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                  Not(WithEventId(notifyArgs.id)),
+                                  Not(WithEventId(leftEnter.getId())),
+                                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    // Send move to the right window, and ensure hover exit and enter are synthesized with new ids.
+    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+                                    {PointF{150, 50}});
+    mDispatcher->notifyMotion(notifyArgs);
+
+    const MotionEvent& leftExit = left->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), Not(WithEventId(notifyArgs.id)),
+                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    right->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                    Not(WithEventId(notifyArgs.id)),
+                                    Not(WithEventId(leftExit.getId())),
+                                    WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
+}
+
 class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
 protected:
     std::shared_ptr<FakeApplicationHandle> mApp;
@@ -7686,12 +7742,15 @@
     static constexpr PointF WINDOW_LOCATION = {20, 20};
 
     void tapOnWindow() {
-        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                   WINDOW_LOCATION));
-        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                 WINDOW_LOCATION));
+        const auto touchingPointer = PointerBuilder(/*id=*/0, ToolType::FINGER)
+                                             .x(WINDOW_LOCATION.x)
+                                             .y(WINDOW_LOCATION.y);
+        mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                          .pointer(touchingPointer)
+                                          .build());
+        mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                          .pointer(touchingPointer)
+                                          .build());
     }
 
     sp<FakeWindowHandle> addSpyWindow() {
@@ -8128,11 +8187,10 @@
     std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
     ASSERT_TRUE(upSequenceNum);
     // Don't finish the events yet, and send a key
-    // Injection is async, so it will succeed
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
-                        /*allowKeyRepeat=*/false));
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                    .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+                    .build());
     // At this point, key is still pending, and should not be sent to the application yet.
     // Make sure the `assertNoEvents` check doesn't take too long. It uses
     // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
@@ -8141,12 +8199,12 @@
 
     // Now tap down again. It should cause the pending key to go to the focused window right away.
     tapOnWindow();
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); // it doesn't matter that we haven't ack'd
-    // the other events yet. We can finish events in any order.
+    mWindow->consumeKeyEvent(WithKeyAction(AKEY_EVENT_ACTION_DOWN)); // it doesn't matter that we
+    // haven't ack'd the other events yet. We can finish events in any order.
     mWindow->finishEvent(*downSequenceNum); // first tap's ACTION_DOWN
     mWindow->finishEvent(*upSequenceNum);   // first tap's ACTION_UP
-    mWindow->consumeMotionDown();
-    mWindow->consumeMotionUp();
+    mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    mWindow->consumeMotionEvent(WithMotionAction(ACTION_UP));
     mWindow->assertNoEvents();
 }
 
@@ -9602,6 +9660,50 @@
     mSecondWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherDragTests, DragAndDropNotCancelledIfSomeOtherPointerIsPilfered) {
+    startDrag();
+
+    // No cancel event after drag start
+    mSpyWindow->assertNoEvents();
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Receives cancel for first pointer after next pointer down
+    mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    mSpyWindow->assertNoEvents();
+
+    // Spy window calls pilfer pointers
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(mSpyWindow->getToken()));
+    mDragWindow->assertNoEvents();
+
+    const MotionEvent firstFingerMoveEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(60).y(60))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Drag window should still receive the new event
+    mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    mDragWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
     startDrag(true, AINPUT_SOURCE_STYLUS);
 
@@ -9939,6 +10041,19 @@
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp());
 }
 
+TEST_F(InputDispatcherDragTests, NoDragAndDropWithHoveringPointer) {
+    // Start hovering over the window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE,
+                                ADISPLAY_ID_DEFAULT, {50, 50}));
+
+    ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
+    ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
+
+    ASSERT_FALSE(startDrag(/*sendDown=*/false))
+            << "Drag and drop should not work with a hovering pointer";
+}
+
 class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};
 
 TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
@@ -9960,6 +10075,7 @@
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
                                                  ADISPLAY_ID_DEFAULT));
+    mDispatcher->waitForIdle();
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
@@ -10837,6 +10953,25 @@
     rightWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) {
+    auto window = createForeground();
+    auto spy = createSpy();
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .deviceId(1)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(100).y(200))
+                    .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Pilfer pointers from the spy window should fail.
+    EXPECT_NE(OK, mDispatcher->pilferPointers(spy->getToken()));
+    spy->assertNoEvents();
+    window->assertNoEvents();
+}
+
 class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
 public:
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 787444c..36be684 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -28,10 +28,13 @@
     mFakePointerController = std::make_shared<FakePointerController>();
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
+    mFakePolicy = sp<FakeInputReaderPolicy>::make();
 
     EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
             .WillRepeatedly(Return(mFakePointerController));
 
+    EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
+
     EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
     InputDeviceIdentifier identifier;
     identifier.name = "device";
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 3f9061f..05b0e97 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -55,6 +55,7 @@
     std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
 
     MockEventHubInterface mMockEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
     std::shared_ptr<FakePointerController> mFakePointerController;
     MockInputReaderContext mMockInputReaderContext;
     std::unique_ptr<InputDevice> mDevice;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5044b3e..e8b779a 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -4922,6 +4922,8 @@
 
 // --- CursorInputMapperTestWithChoreographer ---
 
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
 class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
 protected:
     void SetUp() override {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 68f5857..1efb797 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -50,6 +50,9 @@
 const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
 const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300);
 const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200);
+const auto TOUCHPAD_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER)
+                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
 
 static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
                                               int32_t associatedDisplayId) {
@@ -237,7 +240,7 @@
                     .displayId(DISPLAY_ID)
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
-    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    pc->assertViewportSet(DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedMouse) {
@@ -252,11 +255,11 @@
                     .displayId(DISPLAY_ID)
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
-    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+    pc->assertViewportNotSet();
 
     // After Choreographer gets viewport, PointerController should also have viewport.
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
-    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    pc->assertViewportSet(DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) {
@@ -274,7 +277,7 @@
                     .displayId(ADISPLAY_ID_NONE)
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
-    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    pc->assertViewportSet(DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest,
@@ -291,7 +294,7 @@
                     .displayId(ADISPLAY_ID_NONE)
                     .build());
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
-    ASSERT_EQ(DISPLAY_ID, firstDisplayPc->getDisplayId());
+    firstDisplayPc->assertViewportSet(DISPLAY_ID);
 
     // Change default mouse display. Existing PointerController should be removed.
     mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
@@ -306,7 +309,7 @@
                     .displayId(ADISPLAY_ID_NONE)
                     .build());
     auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
-    ASSERT_EQ(ANOTHER_DISPLAY_ID, secondDisplayPc->getDisplayId());
+    secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) {
@@ -410,7 +413,7 @@
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
-    // Set bounds and initial position of the PointerController.
+    // Set initial position of the PointerController.
     pc->setPosition(100, 200);
 
     // Make NotifyMotionArgs and notify Choreographer.
@@ -461,7 +464,7 @@
     auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
 
-    // Set bounds and initial position for PointerControllers.
+    // Set initial position for PointerControllers.
     unassociatedMousePc->setPosition(100, 200);
     associatedMousePc->setPosition(300, 400);
 
@@ -501,7 +504,7 @@
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
-    // Set bounds and initial position of the PointerController.
+    // Set initial position of the PointerController.
     pc->setPosition(100, 200);
 
     // Assume that pointer capture is enabled.
@@ -549,20 +552,6 @@
     mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
-
-    // Set bounds and initial position of the PointerController.
-    pc->setPosition(100, 200);
-
-    // Make NotifyMotionArgs and notify Choreographer.
-    mChoreographer.notifyMotion(
-            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
-                    .pointer(MOUSE_POINTER)
-                    .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
-                    .build());
-
-    // Check that the PointerController updated the position and the pointer is shown.
-    pc->assertPosition(110, 220);
     ASSERT_TRUE(pc->isPointerShown());
 
     // Enable pointer capture and check if the PointerController hid the pointer.
@@ -641,7 +630,6 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
     mChoreographer.setShowTouchesEnabled(true);
-    assertPointerControllerNotCreated();
     mChoreographer.notifyMotion(
             MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .pointer(FIRST_TOUCH_POINTER)
@@ -885,8 +873,8 @@
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
 
-    // Check that displayId is set.
-    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    // Check that viewport is set for the PointerController.
+    pc->assertViewportSet(DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
@@ -902,14 +890,14 @@
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
 
-    // Check that displayId is unset.
-    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+    // Check that viewport is unset.
+    pc->assertViewportNotSet();
 
     // Set viewport.
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
 
-    // Check that displayId is set.
-    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    // Check that the viewport is set for the PointerController.
+    pc->assertViewportSet(DISPLAY_ID);
 }
 
 TEST_F(PointerChoreographerTest,
@@ -926,14 +914,14 @@
                     .build());
     auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
 
-    // Check that displayId is unset.
-    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+    // Check that viewport is unset.
+    pc->assertViewportNotSet();
 
     // Set viewport which does not match the associated display of the stylus.
     mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID}));
 
-    // Check that displayId is still unset.
-    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+    // Check that viewport is still unset.
+    pc->assertViewportNotSet();
 }
 
 TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
@@ -1050,4 +1038,477 @@
     assertPointerControllerRemoved(pc);
 }
 
+TEST_F(PointerChoreographerTest, WhenTouchpadIsJustAddedDoesNotCreatePointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadEventOccursCreatesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedRemovesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    // Remove the touchpad.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedTouchpad) {
+    // Just adding a viewport or device should not create a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     DISPLAY_ID)}});
+    assertPointerControllerNotCreated();
+
+    // After the touchpad emits event, PointerController will be created and viewport will be set.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedTouchpad) {
+    // Without viewport information, PointerController will be created by a touchpad event
+    // but viewport won't be set.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportNotSet();
+
+    // After Choreographer gets viewport, PointerController should also have viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultTouchpadViewportForPointerController) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // For a touchpad event without a target display, default viewport should be set for
+    // the PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenDefaultTouchpadDisplayChangesSetsDefaultTouchpadViewportForPointerController) {
+    // Set one display as a default touchpad display and emit touchpad event to create
+    // PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    firstDisplayPc->assertViewportSet(DISPLAY_ID);
+
+    // Change default mouse display. Existing PointerController should be removed.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    // New PointerController for the new default display will be created by the motion event.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    assertPointerControllerCreated(ControllerType::MOUSE);
+
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterTouchpadCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotNotified();
+
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenDefaultMouseDisplayChangesTouchpadCallsNotifyPointerDisplayIdChanged) {
+    // Add two viewports.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+    // Set one viewport as a default mouse display ID.
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
+    // before a touchpad event.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    // After a touchpad event, pointer display ID will be notified with new default mouse display.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Notify motion with fake fingers, as if it is multi-finger swipe.
+    // Check if the position of the PointerController is added to the fake finger coords.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithCoords(0, 200), WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                   (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+                  WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                   (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+                  WithPointerCoords(2, 200, 200), WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-90).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 10, 210), WithPointerCoords(1, 110, 210),
+                  WithPointerCoords(2, 210, 210), WithCursorPosition(100, 200)));
+}
+
+TEST_F(PointerChoreographerTest,
+       AssociatedTouchpadMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+    // Add two displays and set one to default.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Add two devices, one unassociated and the other associated with non-default mouse display.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ANOTHER_DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+    // Set initial positions for PointerControllers.
+    unassociatedMousePc->setPosition(100, 200);
+    associatedMousePc->setPosition(300, 400);
+
+    // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the status of the PointerControllers.
+    unassociatedMousePc->assertPosition(100, 200);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    associatedMousePc->assertPosition(310, 420);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+    ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+    // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+                  WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(200, 300);
+
+    // Assume that pointer capture is enabled.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+    // Notify motion as if pointer capture is enabled.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
+                                        .pointer(FIRST_TOUCH_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(ADISPLAY_ID_NONE)
+                                        .build());
+
+    // Check that there's no update on the PointerController.
+    pc->assertPosition(200, 300);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    // Check x-y cooridnates, displayId and cursor position are not changed.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE),
+                  WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                     AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Enable pointer capture and check if the PointerController hid the pointer.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index a0a0f5a..66fdaa4 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -492,6 +492,71 @@
     return WithKeyCodeMatcher(keyCode);
 }
 
+/// EventId
+class WithEventIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithEventIdMatcher(int32_t eventId) : mEventId(eventId) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mEventId == args.id;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mEventId == args.id;
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mEventId == event.getId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with eventId 0x" << std::hex << mEventId; }
+
+    void DescribeNegationTo(std::ostream* os) const {
+        *os << "with eventId not equal to 0x" << std::hex << mEventId;
+    }
+
+private:
+    const int32_t mEventId;
+};
+
+inline WithEventIdMatcher WithEventId(int32_t eventId) {
+    return WithEventIdMatcher(eventId);
+}
+
+/// EventIdSource
+class WithEventIdSourceMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithEventIdSourceMatcher(IdGenerator::Source eventIdSource)
+          : mEventIdSource(eventIdSource) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(args.id);
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(args.id);
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(event.getId());
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with eventId from source 0x" << std::hex << ftl::to_underlying(mEventIdSource);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong event from source"; }
+
+private:
+    const IdGenerator::Source mEventIdSource;
+};
+
+inline WithEventIdSourceMatcher WithEventIdSource(IdGenerator::Source eventIdSource) {
+    return WithEventIdSourceMatcher(eventIdSource);
+}
+
 MATCHER_P(WithRepeatCount, repeatCount, "KeyEvent with specified repeat count") {
     return arg.getRepeatCount() == repeatCount;
 }
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 6203a1d..8cf738c 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -19,6 +19,7 @@
 #include <android-base/logging.h>
 #include <gtest/gtest.h>
 
+#include <com_android_input_flags.h>
 #include <thread>
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
@@ -36,11 +37,17 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+namespace input_flags = com::android::input::flags;
 
 /**
  * Unit tests for TouchpadInputMapper.
  */
-class TouchpadInputMapperTest : public InputMapperUnitTest {
+class TouchpadInputMapperTestBase : public InputMapperUnitTest {
 protected:
     void SetUp() override {
         InputMapperUnitTest::SetUp();
@@ -104,6 +111,14 @@
     }
 };
 
+class TouchpadInputMapperTest : public TouchpadInputMapperTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        TouchpadInputMapperTestBase::SetUp();
+    }
+};
+
 /**
  * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
  * generated when hovering stops. Currently, it is not.
@@ -153,4 +168,71 @@
     ASSERT_THAT(args, testing::IsEmpty());
 }
 
+class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        TouchpadInputMapperTestBase::SetUp();
+    }
+};
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+/**
+ * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
+ * generated when hovering stops. Currently, it is not.
+ * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
+ * but only after the button is released.
+ */
+TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) {
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+
+    std::list<NotifyArgs> args;
+
+    args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
+    args += process(EV_KEY, BTN_TOUCH, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
+    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
+    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
+    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    // Without this sleep, the test fails.
+    // TODO(b/284133337): Figure out whether this can be removed
+    std::this_thread::sleep_for(std::chrono::milliseconds(20));
+
+    args += process(EV_KEY, BTN_LEFT, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_LEFT});
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    args += process(EV_KEY, BTN_LEFT, 0);
+    setScanCodeState(KeyState::UP, {BTN_LEFT});
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+    // Liftoff
+    args.clear();
+    args += process(EV_ABS, ABS_MT_PRESSURE, 0);
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
+    args += process(EV_KEY, BTN_TOUCH, 0);
+    setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, testing::IsEmpty());
+}
+
 } // namespace android
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 1f72e8b..8b16890 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -19,7 +19,6 @@
         "PowerHalWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
-        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
deleted file mode 100644
index ef723c2..0000000
--- a/services/powermanager/WorkDuration.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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.
- */
-
-#define LOG_TAG "WorkDuration"
-
-#include <android/WorkDuration.h>
-#include <android/performance_hint.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
-
-namespace android::os {
-
-WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
-                           int64_t cpuDurationNanos, int64_t gpuDurationNanos)
-      : workPeriodStartTimestampNanos(startTimestampNanos),
-        actualTotalDurationNanos(totalDurationNanos),
-        actualCpuDurationNanos(cpuDurationNanos),
-        actualGpuDurationNanos(gpuDurationNanos) {}
-
-status_t WorkDuration::writeToParcel(Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-
-    parcel->writeInt64(workPeriodStartTimestampNanos);
-    parcel->writeInt64(actualTotalDurationNanos);
-    parcel->writeInt64(actualCpuDurationNanos);
-    parcel->writeInt64(actualGpuDurationNanos);
-    parcel->writeInt64(timestampNanos);
-    return OK;
-}
-
-status_t WorkDuration::readFromParcel(const Parcel*) {
-    return INVALID_OPERATION;
-}
-
-} // namespace android::os
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
deleted file mode 100644
index 99b5b8b..0000000
--- a/services/powermanager/include/android/WorkDuration.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * 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.
- */
-
-#pragma once
-
-#include <binder/Parcelable.h>
-#include <math.h>
-
-struct AWorkDuration {};
-
-namespace android::os {
-
-/**
- * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
- * binder calls.
- * This file needs to be kept in sync with the WorkDuration in
- * frameworks/base/core/java/android/os/WorkDuration.java
- */
-struct WorkDuration : AWorkDuration, android::Parcelable {
-    WorkDuration() = default;
-    ~WorkDuration() = default;
-
-    WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
-                 int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
-    status_t writeToParcel(Parcel* parcel) const override;
-    status_t readFromParcel(const Parcel* parcel) override;
-
-    inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
-        return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
-                actualTotalDurationNanos == other.actualTotalDurationNanos &&
-                actualCpuDurationNanos == other.actualCpuDurationNanos &&
-                actualGpuDurationNanos == other.actualGpuDurationNanos;
-    }
-
-    bool operator==(const WorkDuration& other) const {
-        return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
-    }
-
-    bool operator!=(const WorkDuration& other) const { return !(*this == other); }
-
-    friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
-        os << "{"
-           << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
-           << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
-           << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
-           << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
-           << ", timestampNanos: " << workDuration.timestampNanos << "}";
-        return os;
-    }
-
-    int64_t workPeriodStartTimestampNanos;
-    int64_t actualTotalDurationNanos;
-    int64_t actualCpuDurationNanos;
-    int64_t actualGpuDurationNanos;
-    int64_t timestampNanos;
-};
-
-} // namespace android::os
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 2740a97..455e623 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -91,6 +91,9 @@
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
 }
 
 cc_library {
@@ -114,6 +117,9 @@
         "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
 }
@@ -150,10 +156,11 @@
         "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
-    // For some reason, libvulkan isn't picked up from librenderengine
-    // Probably ASAN related?
     shared_libs: [
+        // For some reason, libvulkan isn't picked up from librenderengine
+        // Probably ASAN related?
         "libvulkan",
+        "server_configurable_flags",
     ],
     sanitize: {
         hwaddress: true,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
index 9c80cac..a74c5fe 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
@@ -36,7 +36,7 @@
 namespace compositionengine {
 
 /**
- * Encapsulates all the state and functionality for how colors should be
+ * Encapsulates all the states and functionality for how colors should be
  * transformed for a display
  */
 class DisplayColorProfile {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 40ebf44..f1d6f52 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include <compositionengine/LayerFE.h>
+#include <ftl/future.h>
 #include <renderengine/LayerSettings.h>
 #include <ui/Fence.h>
 #include <ui/FenceTime.h>
@@ -61,7 +62,7 @@
 } // namespace impl
 
 /**
- * Encapsulates all the state involved with composing layers for an output
+ * Encapsulates all the states involved with composing layers for an output
  */
 class Output {
 public:
@@ -263,8 +264,15 @@
     // Prepare the output, updating the OutputLayers used in the output
     virtual void prepare(const CompositionRefreshArgs&, LayerFESet&) = 0;
 
-    // Presents the output, finalizing all composition details
-    virtual void present(const CompositionRefreshArgs&) = 0;
+    // Presents the output, finalizing all composition details. This may happen
+    // asynchronously, in which case the returned future must be waited upon.
+    virtual ftl::Future<std::monostate> present(const CompositionRefreshArgs&) = 0;
+
+    // Whether this output can be presented from another thread.
+    virtual bool supportsOffloadPresent() const = 0;
+
+    // Make the next call to `present` run asynchronously.
+    virtual void offloadPresentNextFrame() = 0;
 
     // Enables predicting composition strategy to run client composition earlier
     virtual void setPredictCompositionStrategy(bool) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index de82931..eac5d97 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -62,6 +62,7 @@
     compositionengine::Output::FrameFences presentFrame() override;
     void setExpensiveRenderingExpected(bool) override;
     void finishFrame(GpuCompositionResult&&) override;
+    bool supportsOffloadPresent() const override;
 
     // compositionengine::Display overrides
     DisplayId getId() const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index d95fbea..ec6a4e9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -80,7 +80,9 @@
     void setReleasedLayers(ReleasedLayers&&) override;
 
     void prepare(const CompositionRefreshArgs&, LayerFESet&) override;
-    void present(const CompositionRefreshArgs&) override;
+    ftl::Future<std::monostate> present(const CompositionRefreshArgs&) override;
+    bool supportsOffloadPresent() const override { return false; }
+    void offloadPresentNextFrame() override;
 
     void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
     void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override;
@@ -121,6 +123,7 @@
     virtual std::future<bool> chooseCompositionStrategyAsync(
             std::optional<android::HWComposer::DeviceRequestedChanges>*);
     virtual void resetCompositionStrategy();
+    virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync();
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -164,6 +167,7 @@
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
     compositionengine::Output::ColorProfile pickColorProfile(
             const compositionengine::CompositionRefreshArgs&) const;
+    void updateHwcAsyncWorker();
 
     std::string mName;
     std::string mNamePlusId;
@@ -177,6 +181,9 @@
     std::unique_ptr<planner::Planner> mPlanner;
     std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker;
 
+    bool mPredictCompositionStrategy = false;
+    bool mOffloadPresent = false;
+
     // Whether the content must be recomposed this frame.
     bool mMustRecompose = false;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 7b0af3a..6c419da 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -90,7 +90,7 @@
     // The source crop for this layer on this output
     FloatRect sourceCrop;
 
-    // The buffer transform to use for this layer o on this output.
+    // The buffer transform to use for this layer on this output.
     Hwc2::Transform bufferTransform{static_cast<Hwc2::Transform>(0)};
 
     // The dataspace for this layer
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index c88fbd6..95ea3a4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -80,7 +80,10 @@
     MOCK_METHOD1(setReleasedLayers, void(ReleasedLayers&&));
 
     MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
-    MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&));
+    MOCK_METHOD1(present,
+                 ftl::Future<std::monostate>(const compositionengine::CompositionRefreshArgs&));
+    MOCK_CONST_METHOD0(supportsOffloadPresent, bool());
+    MOCK_METHOD(void, offloadPresentNextFrame, ());
 
     MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
     MOCK_METHOD2(rebuildLayerStacks,
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index 002177b..748d87b 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -20,6 +20,7 @@
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/CompositionEngine.h>
 #include <compositionengine/impl/Display.h>
+#include <ui/DisplayMap.h>
 
 #include <renderengine/RenderEngine.h>
 #include <utils/Trace.h>
@@ -88,6 +89,33 @@
     return mRefreshStartTime;
 }
 
+namespace {
+int numDisplaysWithOffloadPresentSupport(const CompositionRefreshArgs& args) {
+    if (!FlagManager::getInstance().multithreaded_present() || args.outputs.size() < 2) {
+        return 0;
+    }
+
+    int numEligibleDisplays = 0;
+    // Only run present in multiple threads if all HWC-enabled displays
+    // being refreshed support it.
+    if (!std::all_of(args.outputs.begin(), args.outputs.end(),
+                     [&numEligibleDisplays](const auto& output) {
+                         if (!ftl::Optional(output->getDisplayId())
+                                      .and_then(HalDisplayId::tryCast)) {
+                             // Not HWC-enabled, so it is always
+                             // client-composited.
+                             return true;
+                         }
+                         const bool support = output->supportsOffloadPresent();
+                         numEligibleDisplays += static_cast<int>(support);
+                         return support;
+                     })) {
+        return 0;
+    }
+    return numEligibleDisplays;
+}
+} // namespace
+
 void CompositionEngine::present(CompositionRefreshArgs& args) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
@@ -105,8 +133,36 @@
         }
     }
 
+    // Offloading the HWC call for `present` allows us to simultaneously call it
+    // on multiple displays. This is desirable because these calls block and can
+    // be slow.
+    if (const int numEligibleDisplays = numDisplaysWithOffloadPresentSupport(args);
+        numEligibleDisplays > 1) {
+        // Leave the last eligible display on the main thread, which will
+        // allow it to run concurrently without an extra thread hop.
+        int numToOffload = numEligibleDisplays - 1;
+        for (auto& output : args.outputs) {
+            if (output->supportsOffloadPresent()) {
+                output->offloadPresentNextFrame();
+                if (--numToOffload == 0) {
+                    break;
+                }
+            }
+        }
+    }
+
+    ui::DisplayVector<ftl::Future<std::monostate>> presentFutures;
     for (const auto& output : args.outputs) {
-        output->present(args);
+        presentFutures.push_back(output->present(args));
+    }
+
+    {
+        ATRACE_NAME("Waiting on HWC");
+        for (auto& future : presentFutures) {
+            // TODO(b/185536303): Call ftl::Future::wait() once it exists, since
+            // we do not need the return value of get().
+            future.get();
+        }
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 469fb38..0475881 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -430,4 +430,13 @@
     impl::Output::finishFrame(std::move(result));
 }
 
+bool Display::supportsOffloadPresent() const {
+    if (const auto halDisplayId = HalDisplayId::tryCast(mId)) {
+        const auto& hwc = getCompositionEngine().getHwComposer();
+        return hwc.hasDisplayCapability(*halDisplayId, DisplayCapability::MULTI_THREADED_PRESENT);
+    }
+
+    return false;
+}
+
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 2ae80de..e4d7578 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -427,7 +427,8 @@
     uncacheBuffers(refreshArgs.bufferIdsToUncache);
 }
 
-void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+ftl::Future<std::monostate> Output::present(
+        const compositionengine::CompositionRefreshArgs& refreshArgs) {
     ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
@@ -448,8 +449,26 @@
 
     devOptRepaintFlash(refreshArgs);
     finishFrame(std::move(result));
-    presentFrameAndReleaseLayers();
+    ftl::Future<std::monostate> future;
+    if (mOffloadPresent) {
+        future = presentFrameAndReleaseLayersAsync();
+
+        // Only offload for this frame. The next frame will determine whether it
+        // needs to be offloaded. Leave the HwcAsyncWorker in place. For one thing,
+        // it is currently presenting. Further, it may be needed next frame, and
+        // we don't want to churn.
+        mOffloadPresent = false;
+    } else {
+        presentFrameAndReleaseLayers();
+        future = ftl::yield<std::monostate>({});
+    }
     renderCachedSets(refreshArgs);
+    return future;
+}
+
+void Output::offloadPresentNextFrame() {
+    mOffloadPresent = true;
+    updateHwcAsyncWorker();
 }
 
 void Output::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
@@ -1084,6 +1103,14 @@
     finishPrepareFrame();
 }
 
+ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync() {
+    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
+               presentFrameAndReleaseLayers();
+               return true;
+           })))
+            .then([](bool) { return std::monostate{}; });
+}
+
 std::future<bool> Output::chooseCompositionStrategyAsync(
         std::optional<android::HWComposer::DeviceRequestedChanges>* changes) {
     return mHwComposerAsyncWorker->send(
@@ -1600,8 +1627,15 @@
 }
 
 void Output::setPredictCompositionStrategy(bool predict) {
-    if (predict) {
-        mHwComposerAsyncWorker = std::make_unique<HwcAsyncWorker>();
+    mPredictCompositionStrategy = predict;
+    updateHwcAsyncWorker();
+}
+
+void Output::updateHwcAsyncWorker() {
+    if (mPredictCompositionStrategy || mOffloadPresent) {
+        if (!mHwComposerAsyncWorker) {
+            mHwComposerAsyncWorker = std::make_unique<HwcAsyncWorker>();
+        }
     } else {
         mHwComposerAsyncWorker.reset(nullptr);
     }
@@ -1616,7 +1650,7 @@
     uint64_t outputLayerHash = getState().outputLayerHash;
     editState().lastOutputLayerHash = outputLayerHash;
 
-    if (!getState().isEnabled || !mHwComposerAsyncWorker) {
+    if (!getState().isEnabled || !mPredictCompositionStrategy) {
         ALOGV("canPredictCompositionStrategy disabled");
         return false;
     }
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 60ed660..a451ab2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -20,12 +20,15 @@
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/Output.h>
 #include <compositionengine/mock/OutputLayer.h>
+#include <ftl/future.h>
 #include <gtest/gtest.h>
 #include <renderengine/mock/RenderEngine.h>
 
 #include "MockHWComposer.h"
 #include "TimeStats/TimeStats.h"
 
+#include <variant>
+
 namespace android::compositionengine {
 namespace {
 
@@ -107,10 +110,19 @@
     EXPECT_CALL(*mOutput2, prepare(Ref(mRefreshArgs), _));
     EXPECT_CALL(*mOutput3, prepare(Ref(mRefreshArgs), _));
 
+    if (FlagManager::getInstance().multithreaded_present()) {
+        EXPECT_CALL(*mOutput1, getDisplayId()).WillOnce(Return(std::nullopt));
+        EXPECT_CALL(*mOutput2, getDisplayId()).WillOnce(Return(std::nullopt));
+        EXPECT_CALL(*mOutput3, getDisplayId()).WillOnce(Return(std::nullopt));
+    }
+
     // The last step is to actually present each output.
-    EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs)));
-    EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs)));
-    EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)));
+    EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs)))
+            .WillOnce(Return(ftl::yield<std::monostate>({})));
+    EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs)))
+            .WillOnce(Return(ftl::yield<std::monostate>({})));
+    EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
+            .WillOnce(Return(ftl::yield<std::monostate>({})));
 
     mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
     mEngine.present(mRefreshArgs);
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 5537fcd..5006e7d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -35,6 +35,7 @@
 
 #include <cmath>
 #include <cstdint>
+#include <variant>
 
 #include "CallOrderStateMachineHelper.h"
 #include "MockHWC2.h"
@@ -54,6 +55,7 @@
 using testing::Invoke;
 using testing::IsEmpty;
 using testing::Mock;
+using testing::NiceMock;
 using testing::Pointee;
 using testing::Property;
 using testing::Ref;
@@ -4900,5 +4902,54 @@
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]);
 }
 
+struct OutputPresentFrameAndReleaseLayersAsyncTest : public ::testing::Test {
+    // Piggy-back on OutputPrepareFrameAsyncTest's version to avoid some boilerplate.
+    struct OutputPartialMock : public OutputPrepareFrameAsyncTest::OutputPartialMock {
+        // Set up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD0(presentFrameAndReleaseLayersAsync, ftl::Future<std::monostate>());
+    };
+    OutputPresentFrameAndReleaseLayersAsyncTest() {
+        mOutput->setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+        mOutput->setCompositionEnabled(true);
+        mRefreshArgs.outputs = {mOutput};
+    }
+
+    mock::DisplayColorProfile* mDisplayColorProfile = new NiceMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new NiceMock<mock::RenderSurface>();
+    std::shared_ptr<OutputPartialMock> mOutput{std::make_shared<NiceMock<OutputPartialMock>>()};
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, notCalledWhenNotRequested) {
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()).Times(0);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+
+    mOutput->present(mRefreshArgs);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) {
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+            .WillOnce(Return(ftl::yield<std::monostate>({})));
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(0);
+
+    mOutput->offloadPresentNextFrame();
+    mOutput->present(mRefreshArgs);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledForOneFrame) {
+    ::testing::InSequence inseq;
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+            .WillOnce(Return(ftl::yield<std::monostate>({})));
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+
+    mOutput->offloadPresentNextFrame();
+    mOutput->present(mRefreshArgs);
+    mOutput->present(mRefreshArgs);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 2ffe92b..7fdf9e7 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -63,14 +63,14 @@
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mCompositionDisplay{args.compositionDisplay},
-        mActiveModeFPSTrace(concatId("ActiveModeFPS")),
-        mActiveModeFPSHwcTrace(concatId("ActiveModeFPS_HWC")),
-        mRenderFrameRateFPSTrace(concatId("RenderRateFPS")),
+        mPendingModeFpsTrace(concatId("PendingModeFps")),
+        mActiveModeFpsTrace(concatId("ActiveModeFps")),
+        mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
         mRefreshRateSelector(std::move(args.refreshRateSelector)),
-        mDesiredActiveModeChanged(concatId("DesiredActiveModeChanged"), false) {
+        mDesiredModeChanged(concatId("DesiredModeChanged"), false) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->createRenderSurface(
             compositionengine::RenderSurfaceCreationArgsBuilder()
@@ -210,16 +210,16 @@
 }
 
 void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
-    ATRACE_INT(mActiveModeFPSTrace.c_str(), vsyncRate.getIntValue());
-    ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue());
+    ATRACE_INT(mActiveModeFpsTrace.c_str(), vsyncRate.getIntValue());
+    ATRACE_INT(mRenderRateFpsTrace.c_str(), renderFps.getIntValue());
 
     mRefreshRateSelector->setActiveMode(modeId, renderFps);
     updateRefreshRateOverlayRate(vsyncRate, renderFps);
 }
 
-status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
-                                           const hal::VsyncPeriodChangeConstraints& constraints,
-                                           hal::VsyncPeriodChangeTimeline* outTimeline) {
+bool DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
+                                       const hal::VsyncPeriodChangeConstraints& constraints,
+                                       hal::VsyncPeriodChangeTimeline& outTimeline) {
     if (!info.modeOpt || info.modeOpt->modePtr->getPhysicalDisplayId() != getPhysicalId()) {
         ALOGE("Trying to initiate a mode change to invalid mode %s on display %s",
               info.modeOpt ? std::to_string(info.modeOpt->modePtr->getId().value()).c_str()
@@ -227,14 +227,18 @@
               to_string(getId()).c_str());
         return BAD_VALUE;
     }
-    mUpcomingActiveMode = info;
+    mPendingMode = info;
     mIsModeSetPending = true;
 
-    const auto& pendingMode = *info.modeOpt->modePtr;
-    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getVsyncRate().getIntValue());
+    const auto& mode = *info.modeOpt->modePtr;
 
-    return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), pendingMode.getHwcId(),
-                                                    constraints, outTimeline);
+    if (mHwComposer.setActiveModeWithConstraints(getPhysicalId(), mode.getHwcId(), constraints,
+                                                 &outTimeline) != OK) {
+        return false;
+    }
+
+    ATRACE_INT(mPendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+    return true;
 }
 
 void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
@@ -524,8 +528,7 @@
     }
 }
 
-auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
-        -> DesiredActiveModeAction {
+auto DisplayDevice::setDesiredMode(const ActiveModeInfo& info, bool force) -> DesiredModeAction {
     ATRACE_CALL();
 
     LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided");
@@ -534,49 +537,50 @@
 
     ALOGV("%s(%s)", __func__, to_string(*info.modeOpt->modePtr).c_str());
 
-    std::scoped_lock lock(mActiveModeLock);
-    if (mDesiredActiveModeChanged) {
-        // If a mode change is pending, just cache the latest request in mDesiredActiveMode
-        const auto prevConfig = mDesiredActiveMode.event;
-        mDesiredActiveMode = info;
-        mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig;
-        return DesiredActiveModeAction::None;
+    std::scoped_lock lock(mDesiredModeLock);
+    if (mDesiredModeChanged) {
+        // A mode transition was already scheduled, so just override the desired mode.
+        const auto event = mDesiredMode.event;
+        mDesiredMode = info;
+        mDesiredMode.event = mDesiredMode.event | event;
+        return DesiredModeAction::None;
     }
 
     const auto& desiredMode = *info.modeOpt->modePtr;
 
-    // Check if we are already at the desired mode
-    const auto currentMode = refreshRateSelector().getActiveMode();
-    if (!force && currentMode.modePtr->getId() == desiredMode.getId()) {
-        if (currentMode == info.modeOpt) {
-            return DesiredActiveModeAction::None;
+    // If the desired mode is already active...
+    const auto activeMode = refreshRateSelector().getActiveMode();
+    if (!force && activeMode.modePtr->getId() == desiredMode.getId()) {
+        if (activeMode == info.modeOpt) {
+            return DesiredModeAction::None;
         }
 
+        // ...but the render rate changed:
         setActiveMode(desiredMode.getId(), desiredMode.getVsyncRate(), info.modeOpt->fps);
-        return DesiredActiveModeAction::InitiateRenderRateSwitch;
+        return DesiredModeAction::InitiateRenderRateSwitch;
     }
 
-    // Set the render frame rate to the current physical refresh rate to schedule the next
+    // Set the render frame rate to the active physical refresh rate to schedule the next
     // frame as soon as possible.
-    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getVsyncRate(),
-                  currentMode.modePtr->getVsyncRate());
+    setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
+                  activeMode.modePtr->getVsyncRate());
 
     // Initiate a mode change.
-    mDesiredActiveModeChanged = true;
-    mDesiredActiveMode = info;
-    return DesiredActiveModeAction::InitiateDisplayModeSwitch;
+    mDesiredModeChanged = true;
+    mDesiredMode = info;
+    return DesiredModeAction::InitiateDisplayModeSwitch;
 }
 
-std::optional<DisplayDevice::ActiveModeInfo> DisplayDevice::getDesiredActiveMode() const {
-    std::scoped_lock lock(mActiveModeLock);
-    if (mDesiredActiveModeChanged) return mDesiredActiveMode;
+auto DisplayDevice::getDesiredMode() const -> ftl::Optional<ActiveModeInfo> {
+    std::scoped_lock lock(mDesiredModeLock);
+    if (mDesiredModeChanged) return mDesiredMode;
     return std::nullopt;
 }
 
-void DisplayDevice::clearDesiredActiveModeState() {
-    std::scoped_lock lock(mActiveModeLock);
-    mDesiredActiveMode.event = scheduler::DisplayModeEvent::None;
-    mDesiredActiveModeChanged = false;
+void DisplayDevice::clearDesiredMode() {
+    std::scoped_lock lock(mDesiredModeLock);
+    mDesiredMode.event = scheduler::DisplayModeEvent::None;
+    mDesiredModeChanged = false;
 }
 
 void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index a40f310..a061fca 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include <memory>
-#include <optional>
 #include <string>
 #include <unordered_map>
 
@@ -25,6 +24,7 @@
 #include <android/native_window.h>
 #include <binder/IBinder.h>
 #include <ftl/concat.h>
+#include <ftl/optional.h>
 #include <gui/LayerState.h>
 #include <math/mat4.h>
 #include <renderengine/RenderEngine.h>
@@ -51,6 +51,7 @@
 #include "ThreadContext.h"
 #include "TracedOrdinal.h"
 #include "Utils/Dumper.h"
+
 namespace android {
 
 class Fence;
@@ -205,19 +206,15 @@
         }
     };
 
-    enum class DesiredActiveModeAction {
-        None,
-        InitiateDisplayModeSwitch,
-        InitiateRenderRateSwitch
-    };
-    DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&, bool force = false)
-            EXCLUDES(mActiveModeLock);
-    std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
-    void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
-    ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
-        return mUpcomingActiveMode;
-    }
+    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
 
+    DesiredModeAction setDesiredMode(const ActiveModeInfo&, bool force = false)
+            EXCLUDES(mDesiredModeLock);
+
+    ftl::Optional<ActiveModeInfo> getDesiredMode() const EXCLUDES(mDesiredModeLock);
+    void clearDesiredMode() EXCLUDES(mDesiredModeLock);
+
+    ActiveModeInfo getPendingMode() const REQUIRES(kMainThreadContext) { return mPendingMode; }
     bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; }
 
     scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
@@ -226,9 +223,8 @@
 
     void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
 
-    status_t initiateModeChange(const ActiveModeInfo&,
-                                const hal::VsyncPeriodChangeConstraints& constraints,
-                                hal::VsyncPeriodChangeTimeline* outTimeline)
+    bool initiateModeChange(const ActiveModeInfo&, const hal::VsyncPeriodChangeConstraints&,
+                            hal::VsyncPeriodChangeTimeline& outTimeline)
             REQUIRES(kMainThreadContext);
 
     void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
@@ -282,9 +278,9 @@
     const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
 
     std::string mDisplayName;
-    std::string mActiveModeFPSTrace;
-    std::string mActiveModeFPSHwcTrace;
-    std::string mRenderFrameRateFPSTrace;
+    std::string mPendingModeFpsTrace;
+    std::string mActiveModeFpsTrace;
+    std::string mRenderRateFpsTrace;
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
@@ -319,11 +315,11 @@
     // This parameter is only used for hdr/sdr ratio overlay
     float mHdrSdrRatio = 1.0f;
 
-    mutable std::mutex mActiveModeLock;
-    ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
-    TracedOrdinal<bool> mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock);
+    mutable std::mutex mDesiredModeLock;
+    ActiveModeInfo mDesiredMode GUARDED_BY(mDesiredModeLock);
+    TracedOrdinal<bool> mDesiredModeChanged GUARDED_BY(mDesiredModeLock);
 
-    ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
+    ActiveModeInfo mPendingMode GUARDED_BY(kMainThreadContext);
     bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
 };
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 2d957e6..cc2f6c7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1575,8 +1575,7 @@
 }
 
 bool AidlComposer::hasMultiThreadedPresentSupport(Display display) {
-#if 0
-    // TODO (b/259132483): Reenable
+    if (!FlagManager::getInstance().multithreaded_present()) return false;
     const auto displayId = translate<int64_t>(display);
     std::vector<AidlDisplayCapability> capabilities;
     const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities);
@@ -1586,10 +1585,6 @@
     }
     return std::find(capabilities.begin(), capabilities.end(),
                      AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end();
-#else
-    (void) display;
-    return false;
-#endif
 }
 
 void AidlComposer::addReader(Display display) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 9aaaa95..26c0d67 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -945,6 +945,10 @@
                                                      std::optional<Period> timeoutOpt) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     auto& displayData = mDisplayData[displayId];
+    if (!displayData.hwcDisplay) {
+        // Display setup has not completed yet
+        return BAD_INDEX;
+    }
     {
         std::scoped_lock lock{displayData.expectedPresentLock};
         const auto lastExpectedPresentTimestamp = displayData.lastExpectedPresentTimestamp;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index e005ad3..f00ef67 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -251,12 +251,7 @@
     actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
     mActualDuration = actualDuration;
     WorkDuration duration;
-    duration.workPeriodStartTimestampNanos = mCommitStartTimes[0].ns();
-    // TODO(b/284324521): Correctly calculate total duration.
     duration.durationNanos = actualDuration->ns();
-    duration.cpuDurationNanos = actualDuration->ns();
-    // TODO(b/284324521): Calculate RenderEngine GPU time.
-    duration.gpuDurationNanos = 0;
     duration.timeStampNanos = TimePoint::now().ns();
     mHintSessionQueue.push_back(duration);
 
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 2d4ec04..29c9432 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -28,6 +28,7 @@
     ],
     static_libs: [
         "libperfetto_client_experimental",
+        "libsurfaceflinger_common",
     ],
     export_include_dirs: ["."],
 }
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index dfbb55d..9dc3938 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -21,6 +21,7 @@
 #include "FrameTimeline.h"
 
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
@@ -1200,7 +1201,9 @@
         surfaceFrame->trace(mToken, monoBootOffset);
     }
 
-    addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime);
+    if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime);
+    }
 }
 
 float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index a826ec1..0983e7c 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -433,4 +433,15 @@
     }
 }
 
+bool LayerLifecycleManager::isLayerSecure(uint32_t layerId) const {
+    if (layerId == UNASSIGNED_LAYER_ID) {
+        return false;
+    }
+
+    if (getLayerFromId(layerId)->flags & layer_state_t::eLayerSecure) {
+        return true;
+    }
+    return isLayerSecure(getLayerFromId(layerId)->parentId);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index 9aff78e..330da9a 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -80,6 +80,7 @@
     const std::vector<RequestedLayerState*>& getChangedLayers() const;
     const ftl::Flags<RequestedLayerState::Changes> getGlobalChanges() const;
     const RequestedLayerState* getLayerFromId(uint32_t) const;
+    bool isLayerSecure(uint32_t) const;
 
 private:
     friend class LayerLifecycleManagerTest;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 9476ff4..743cbf3 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -365,7 +365,7 @@
     return snapshot;
 }
 
-LayerSnapshotBuilder::LayerSnapshotBuilder() : mRootSnapshot(getRootSnapshot()) {}
+LayerSnapshotBuilder::LayerSnapshotBuilder() {}
 
 LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() {
     args.forceUpdate = ForceUpdateFlags::ALL;
@@ -417,19 +417,20 @@
 
 void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
     ATRACE_NAME("UpdateSnapshots");
+    LayerSnapshot rootSnapshot = args.rootSnapshot;
     if (args.parentCrop) {
-        mRootSnapshot.geomLayerBounds = *args.parentCrop;
+        rootSnapshot.geomLayerBounds = *args.parentCrop;
     } else if (args.forceUpdate == ForceUpdateFlags::ALL || args.displayChanges) {
-        mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
+        rootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
     }
     if (args.displayChanges) {
-        mRootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
+        rootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
                 RequestedLayerState::Changes::Geometry;
     }
     if (args.forceUpdate == ForceUpdateFlags::HIERARCHY) {
-        mRootSnapshot.changes |=
+        rootSnapshot.changes |=
                 RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility;
-        mRootSnapshot.clientChanges |= layer_state_t::eReparent;
+        rootSnapshot.clientChanges |= layer_state_t::eReparent;
     }
 
     for (auto& snapshot : mSnapshots) {
@@ -444,13 +445,13 @@
         // multiple children.
         LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id,
                                                                 LayerHierarchy::Variant::Attached);
-        updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot, /*depth=*/0);
+        updateSnapshotsInHierarchy(args, args.root, root, rootSnapshot, /*depth=*/0);
     } else {
         for (auto& [childHierarchy, variant] : args.root.mChildren) {
             LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
                                                                     childHierarchy->getLayer()->id,
                                                                     variant);
-            updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot, /*depth=*/0);
+            updateSnapshotsInHierarchy(args, *childHierarchy, root, rootSnapshot, /*depth=*/0);
         }
     }
 
@@ -459,7 +460,6 @@
     updateTouchableRegionCrop(args);
 
     const bool hasUnreachableSnapshots = sortSnapshotsByZ(args);
-    clearChanges(mRootSnapshot);
 
     // Destroy unreachable snapshots for clone layers. And destroy snapshots for non-clone
     // layers if the layer have been destroyed.
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 1506913..1cec018 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -33,6 +33,9 @@
 // The builder also uses a fast path to update
 // snapshots when there are only buffer updates.
 class LayerSnapshotBuilder {
+private:
+    static LayerSnapshot getRootSnapshot();
+
 public:
     enum class ForceUpdateFlags {
         NONE,
@@ -55,6 +58,7 @@
         const std::unordered_map<std::string, bool>& supportedLayerGenericMetadata;
         const std::unordered_map<std::string, uint32_t>& genericLayerMetadataKeyMap;
         bool skipRoundCornersWhenProtected = false;
+        LayerSnapshot rootSnapshot = getRootSnapshot();
     };
     LayerSnapshotBuilder();
 
@@ -87,7 +91,6 @@
 
 private:
     friend class LayerSnapshotTest;
-    static LayerSnapshot getRootSnapshot();
 
     // return true if we were able to successfully update the snapshots via
     // the fast path.
@@ -130,7 +133,6 @@
     std::unordered_set<LayerHierarchy::TraversalPath, LayerHierarchy::TraversalPathHash>
             mNeedsTouchableRegionCrop;
     std::vector<std::unique_ptr<LayerSnapshot>> mSnapshots;
-    LayerSnapshot mRootSnapshot;
     bool mResortSnapshots = false;
     int mNumInterestingSnapshots = 0;
 };
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 0e49b75..b1a18ae 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -98,7 +98,6 @@
     z = 0;
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
-    dataspace = ui::Dataspace::UNKNOWN;
     desiredHdrSdrRatio = 1.f;
     currentHdrSdrRatio = 1.f;
     dataspaceRequested = false;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 4c2da91..9195b57 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1718,10 +1718,18 @@
     StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
                   crop.bottom);
     const auto frameRate = snapshot.frameRate;
+    std::string frameRateStr;
+    if (frameRate.vote.rate.isValid()) {
+        StringAppendF(&frameRateStr, "%.2f", frameRate.vote.rate.getValue());
+    }
     if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "%s %15s %17s", to_string(frameRate.vote.rate).c_str(),
+        StringAppendF(&result, "%6s %15s %17s", frameRateStr.c_str(),
                       ftl::enum_string(frameRate.vote.type).c_str(),
                       ftl::enum_string(frameRate.vote.seamlessness).c_str());
+    } else if (frameRate.category != FrameRateCategory::Default) {
+        StringAppendF(&result, "%6s %15s %17s", frameRateStr.c_str(),
+                      (std::string("Cat::") + ftl::enum_string(frameRate.category)).c_str(),
+                      ftl::enum_string(frameRate.vote.seamlessness).c_str());
     } else {
         result.append(41, ' ');
     }
@@ -4326,7 +4334,6 @@
         prepareBasicGeometryCompositionState();
         prepareGeometryCompositionState();
         snapshot->roundedCorner = getRoundedCornerState();
-        snapshot->stretchEffect = getStretchEffect();
         snapshot->transformedBounds = mScreenBounds;
         if (mEffectiveShadowRadius > 0.f) {
             snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings;
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index 6d2586a..d714848 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -21,6 +21,7 @@
         "libui",
         "libutils",
     ],
+    static_libs: ["libsurfaceflinger_common"],
 }
 
 cc_library_headers {
@@ -61,5 +62,9 @@
         "libgmock",
         "libgtest",
         "libscheduler",
+        "libsurfaceflingerflags_test",
+    ],
+    shared_libs: [
+        "server_configurable_flags",
     ],
 }
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 47c8ef9..6a7063e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -36,7 +36,6 @@
 #include <scheduler/FrameRateMode.h>
 #include <utils/Trace.h>
 
-#include "../SurfaceFlingerProperties.h"
 #include "RefreshRateSelector.h"
 
 #include <com_android_graphics_surfaceflinger_flags.h>
@@ -971,17 +970,21 @@
 }
 
 ftl::Optional<FrameRateMode> RefreshRateSelector::onKernelTimerChanged(
-        std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const {
+        ftl::Optional<DisplayModeId> desiredModeIdOpt, bool timerExpired) const {
     std::lock_guard lock(mLock);
 
-    const auto current = [&]() REQUIRES(mLock) -> FrameRateMode {
-        if (desiredActiveModeId) {
-            const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get();
-            return FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
-        }
-
-        return getActiveModeLocked();
-    }();
+    const auto current =
+            desiredModeIdOpt
+                    .and_then([this](DisplayModeId modeId)
+                                      REQUIRES(mLock) { return mDisplayModes.get(modeId); })
+                    .transform([](const DisplayModePtr& modePtr) {
+                        return FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
+                    })
+                    .or_else([this] {
+                        ftl::FakeGuard guard(mLock);
+                        return std::make_optional(getActiveModeLocked());
+                    })
+                    .value();
 
     const DisplayModePtr& min = mMinRefreshRateModeIt->second;
     if (current.modePtr->getId() == min->getId()) {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 545b939..40e9a83 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -16,9 +16,6 @@
 
 #pragma once
 
-#include <algorithm>
-#include <numeric>
-#include <set>
 #include <type_traits>
 #include <utility>
 #include <variant>
@@ -258,9 +255,8 @@
                 mMaxRefreshRateModeIt->second->getPeakFps()};
     }
 
-    ftl::Optional<FrameRateMode> onKernelTimerChanged(
-            std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const
-            EXCLUDES(mLock);
+    ftl::Optional<FrameRateMode> onKernelTimerChanged(ftl::Optional<DisplayModeId> desiredModeIdOpt,
+                                                      bool timerExpired) const EXCLUDES(mLock);
 
     void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index b54f334..6610f94 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -118,7 +118,7 @@
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
     auto schedulePtr = std::make_shared<VsyncSchedule>(
-            displayId, mFeatures,
+            selectorPtr->getActiveMode().modePtr, mFeatures,
             [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
             mVsyncTrackerCallback);
 
@@ -181,32 +181,33 @@
              .expectedVsyncTime = expectedVsyncTime,
              .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration};
 
-    LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId);
-    const auto pacesetterId = *mPacesetterDisplayId;
-    const auto pacesetterOpt = mDisplays.get(pacesetterId);
+    ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked();
+    pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr);
 
-    FrameTargeter& pacesetterTargeter = *pacesetterOpt->get().targeterPtr;
-    pacesetterTargeter.beginFrame(beginFrameArgs, *pacesetterOpt->get().schedulePtr);
+    {
+        FrameTargets targets;
+        targets.try_emplace(pacesetterPtr->displayId, &pacesetterPtr->targeterPtr->target());
 
-    FrameTargets targets;
-    targets.try_emplace(pacesetterId, &pacesetterTargeter.target());
+        for (const auto& [id, display] : mDisplays) {
+            if (id == pacesetterPtr->displayId) continue;
 
-    for (const auto& [id, display] : mDisplays) {
-        if (id == pacesetterId) continue;
+            FrameTargeter& targeter = *display.targeterPtr;
+            targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
+            targets.try_emplace(id, &targeter.target());
+        }
 
-        FrameTargeter& targeter = *display.targeterPtr;
-        targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
-        targets.try_emplace(id, &targeter.target());
+        if (!compositor.commit(pacesetterPtr->displayId, targets)) return;
     }
 
-    if (!compositor.commit(pacesetterId, targets)) return;
+    // The pacesetter may have changed or been registered anew during commit.
+    pacesetterPtr = pacesetterPtrLocked();
 
     // TODO(b/256196556): Choose the frontrunner display.
     FrameTargeters targeters;
-    targeters.try_emplace(pacesetterId, &pacesetterTargeter);
+    targeters.try_emplace(pacesetterPtr->displayId, pacesetterPtr->targeterPtr.get());
 
     for (auto& [id, display] : mDisplays) {
-        if (id == pacesetterId) continue;
+        if (id == pacesetterPtr->displayId) continue;
 
         FrameTargeter& targeter = *display.targeterPtr;
         targeters.try_emplace(id, &targeter);
@@ -214,7 +215,7 @@
 
     if (FlagManager::getInstance().vrr_config() &&
         CC_UNLIKELY(mPacesetterFrameDurationFractionToSkip > 0.f)) {
-        const auto period = pacesetterTargeter.target().expectedFrameDuration();
+        const auto period = pacesetterPtr->targeterPtr->target().expectedFrameDuration();
         const auto skipDuration = Duration::fromNs(
                 static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
         ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
@@ -223,7 +224,19 @@
         mPacesetterFrameDurationFractionToSkip = 0.f;
     }
 
-    const auto resultsPerDisplay = compositor.composite(pacesetterId, targeters);
+    if (FlagManager::getInstance().vrr_config()) {
+        const auto minFramePeriod = pacesetterPtr->schedulePtr->minFramePeriod();
+        const auto presentFenceForPastVsync =
+                pacesetterPtr->targeterPtr->target().presentFenceForPastVsync(minFramePeriod);
+        const auto lastConfirmedPresentTime = presentFenceForPastVsync->getSignalTime();
+        if (lastConfirmedPresentTime != Fence::SIGNAL_TIME_PENDING &&
+            lastConfirmedPresentTime != Fence::SIGNAL_TIME_INVALID) {
+            pacesetterPtr->schedulePtr->getTracker()
+                    .onFrameBegin(expectedVsyncTime, TimePoint::fromNs(lastConfirmedPresentTime));
+        }
+    }
+
+    const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
     compositor.sample();
 
     for (const auto& [id, targeter] : targeters) {
@@ -503,7 +516,7 @@
 }
 
 void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable,
-                                            std::optional<Fps> refreshRate) {
+                                            DisplayModePtr modePtr) {
     const auto displayOpt = mDisplays.get(id);
     if (!displayOpt) {
         ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
@@ -512,12 +525,12 @@
     const Display& display = *displayOpt;
 
     if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
-        if (!refreshRate) {
-            refreshRate = display.selectorPtr->getActiveMode().modePtr->getVsyncRate();
+        if (!modePtr) {
+            modePtr = display.selectorPtr->getActiveMode().modePtr.get();
         }
-        if (refreshRate->isValid()) {
+        if (modePtr->getVsyncRate().isValid()) {
             constexpr bool kForce = false;
-            display.schedulePtr->startPeriodTransition(refreshRate->getPeriod(), kForce);
+            display.schedulePtr->onDisplayModeChanged(ftl::as_non_null(modePtr), kForce);
         }
     }
 }
@@ -563,19 +576,7 @@
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getVsyncRate()).c_str());
 
-    display.schedulePtr->getTracker().setDisplayModeData(
-            {.renderRate = renderFrameRate,
-             .notifyExpectedPresentTimeoutOpt = getNotifyExpectedPresentTimeout(mode)});
-}
-
-std::optional<Period> Scheduler::getNotifyExpectedPresentTimeout(const FrameRateMode& mode) {
-    if (mode.modePtr->getVrrConfig() && mode.modePtr->getVrrConfig()->notifyExpectedPresentConfig) {
-        return Period::fromNs(
-                mode.modePtr->getVrrConfig()
-                        ->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
-    } else {
-        return std::nullopt;
-    }
+    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
 
 void Scheduler::resync() {
@@ -913,9 +914,9 @@
 
         newVsyncSchedulePtr = pacesetter.schedulePtr;
 
-        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getVsyncRate();
         constexpr bool kForce = true;
-        newVsyncSchedulePtr->startPeriodTransition(refreshRate.getPeriod(), kForce);
+        newVsyncSchedulePtr->onDisplayModeChanged(pacesetter.selectorPtr->getActiveMode().modePtr,
+                                                  kForce);
     }
     return newVsyncSchedulePtr;
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index c78051a..8a76436 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -33,6 +33,7 @@
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
 #include <ftl/fake_guard.h>
+#include <ftl/non_null.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
 #include <scheduler/FrameTargeter.h>
@@ -210,13 +211,12 @@
     // If allowToEnable is true, then hardware vsync will be turned on.
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
-    // If refreshRate is nullopt, use the existing refresh rate of the display.
+    // If modePtr is nullopt, use the active display mode.
     void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable,
-                               std::optional<Fps> refreshRate = std::nullopt)
-            EXCLUDES(mDisplayLock) {
+                               DisplayModePtr modePtr = nullptr) EXCLUDES(mDisplayLock) {
         std::scoped_lock lock(mDisplayLock);
         ftl::FakeGuard guard(kMainThreadContext);
-        resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
+        resyncToHardwareVsyncLocked(id, allowToEnable, modePtr);
     }
     void forceNextResync() { mLastResyncTime = 0; }
 
@@ -354,7 +354,7 @@
     void onHardwareVsyncRequest(PhysicalDisplayId, bool enable);
 
     void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable,
-                                     std::optional<Fps> refreshRate = std::nullopt)
+                                     DisplayModePtr modePtr = nullptr)
             REQUIRES(kMainThreadContext, mDisplayLock);
     void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock);
     void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
@@ -431,9 +431,6 @@
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
 
-    std::optional<Period> getNotifyExpectedPresentTimeout(const FrameRateMode&)
-            REQUIRES(mDisplayLock);
-
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
         sp<EventThreadConnection> connection;
@@ -520,13 +517,17 @@
                                                      });
     }
 
+    // The pacesetter must exist as a precondition.
+    ftl::NonNull<const Display*> pacesetterPtrLocked() const REQUIRES(mDisplayLock) {
+        return ftl::as_non_null(&pacesetterDisplayLocked()->get());
+    }
+
     RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) {
         std::scoped_lock lock(mDisplayLock);
         return pacesetterSelectorPtrLocked();
     }
 
     RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) {
-        ftl::FakeGuard guard(kMainThreadContext);
         return pacesetterDisplayLocked()
                 .transform([](const Display& display) { return display.selectorPtr; })
                 .or_else([] { return std::optional<RefreshRateSelectorPtr>(nullptr); })
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index f5f93ce..7379a46 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -47,16 +47,16 @@
 
 VSyncPredictor::~VSyncPredictor() = default;
 
-VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
+VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
                                size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
                                IVsyncTrackerCallback& callback)
-      : mId(id),
+      : mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
         mVsyncTrackerCallback(callback),
-        mIdealPeriod(idealPeriod) {
+        mDisplayModePtr(modePtr) {
     resetModel();
 }
 
@@ -74,13 +74,18 @@
     return (i + 1) % mTimestamps.size();
 }
 
+nsecs_t VSyncPredictor::idealPeriod() const {
+    return mDisplayModePtr->getVsyncRate().getPeriodNsecs();
+}
+
 bool VSyncPredictor::validate(nsecs_t timestamp) const {
     if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
         return true;
     }
 
-    auto const aValidTimestamp = mTimestamps[mLastTimestampIndex];
-    auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
+    const auto aValidTimestamp = mTimestamps[mLastTimestampIndex];
+    const auto percent =
+            (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent &&
         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
         return false;
@@ -90,7 +95,7 @@
                                        [timestamp](nsecs_t a, nsecs_t b) {
                                            return std::abs(timestamp - a) < std::abs(timestamp - b);
                                        });
-    const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / mIdealPeriod;
+    const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
     if (distancePercent < kOutlierTolerancePercent) {
         // duplicate timestamp
         return false;
@@ -100,7 +105,24 @@
 
 nsecs_t VSyncPredictor::currentPeriod() const {
     std::lock_guard lock(mMutex);
-    return mRateMap.find(mIdealPeriod)->second.slope;
+    return mRateMap.find(idealPeriod())->second.slope;
+}
+
+Period VSyncPredictor::minFramePeriod() const {
+    if (!FlagManager::getInstance().vrr_config()) {
+        return Period::fromNs(currentPeriod());
+    }
+
+    std::lock_guard lock(mMutex);
+    return minFramePeriodLocked();
+}
+
+Period VSyncPredictor::minFramePeriodLocked() const {
+    const auto idealPeakRefreshPeriod = mDisplayModePtr->getPeakFps().getPeriodNsecs();
+    const auto numPeriods = static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
+                                                        static_cast<float>(idealPeriod())));
+    const auto slope = mRateMap.find(idealPeriod())->second.slope;
+    return Period::fromNs(slope * numPeriods);
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -137,7 +159,7 @@
 
     const size_t numSamples = mTimestamps.size();
     if (numSamples < kMinimumSamplesForPrediction) {
-        mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
+        mRateMap[idealPeriod()] = {idealPeriod(), 0};
         return true;
     }
 
@@ -161,7 +183,7 @@
 
     // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
     const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
-    auto it = mRateMap.find(mIdealPeriod);
+    auto it = mRateMap.find(idealPeriod());
     auto const currentPeriod = it->second.slope;
 
     // The mean of the ordinals must be precise for the intercept calculation, so scale them up for
@@ -199,7 +221,7 @@
     }
 
     if (CC_UNLIKELY(bottom == 0)) {
-        it->second = {mIdealPeriod, 0};
+        it->second = {idealPeriod(), 0};
         clearTimestamps();
         return false;
     }
@@ -207,9 +229,9 @@
     nsecs_t const anticipatedPeriod = top * kScalingFactor / bottom;
     nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
 
-    auto const percent = std::abs(anticipatedPeriod - mIdealPeriod) * kMaxPercent / mIdealPeriod;
+    auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent) {
-        it->second = {mIdealPeriod, 0};
+        it->second = {idealPeriod(), 0};
         clearTimestamps();
         return false;
     }
@@ -241,8 +263,8 @@
     if (mTimestamps.empty()) {
         traceInt64("VSP-mode", 1);
         auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
-        auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
-        return knownTimestamp + numPeriodsOut * mIdealPeriod;
+        auto const numPeriodsOut = ((timePoint - knownTimestamp) / idealPeriod()) + 1;
+        return knownTimestamp + numPeriodsOut * idealPeriod();
     }
 
     auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());
@@ -278,27 +300,32 @@
     mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
 
     const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
-        if (!mDisplayModeDataOpt) return 0;
-
+        if (!mRenderRateOpt) return 0;
         const auto divisor =
-                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod),
-                                                         mDisplayModeDataOpt->renderRate);
+                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
+                                                         *mRenderRateOpt);
         if (divisor <= 1) return 0;
 
-        const int mod = mLastVsyncSequence->seq % divisor;
+        int mod = mLastVsyncSequence->seq % divisor;
         if (mod == 0) return 0;
 
+        // This is actually a bug fix, but guarded with vrr_config since we found it with this
+        // config
+        if (FlagManager::getInstance().vrr_config()) {
+            if (mod < 0) mod += divisor;
+        }
+
         return divisor - mod;
     }();
 
     if (renderRatePhase == 0) {
         const auto vsyncTime = mLastVsyncSequence->vsyncTime;
-        if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
+        if (FlagManager::getInstance().vrr_config()) {
             const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
             ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__,
                           ticks<std::milli, float>(vsyncTimePoint - TimePoint::now()));
-            mVsyncTrackerCallback.onVsyncGenerated(mId, vsyncTimePoint, *mDisplayModeDataOpt,
-                                                   Period::fromNs(mIdealPeriod));
+            const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+            mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
         }
         return vsyncTime;
     }
@@ -307,12 +334,13 @@
     const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
     const auto nextAnticipatedVsyncTime =
             nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
-    if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
+    if (FlagManager::getInstance().vrr_config()) {
         const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime);
         ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__,
                       ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now()));
-        mVsyncTrackerCallback.onVsyncGenerated(mId, nextAnticipatedVsyncTimePoint,
-                                               *mDisplayModeDataOpt, Period::fromNs(mIdealPeriod));
+        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+        mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr,
+                                               renderRate);
     }
     return nextAnticipatedVsyncTime;
 }
@@ -328,7 +356,8 @@
 bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
     std::lock_guard lock(mMutex);
     const auto divisor =
-            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
+            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
+                                                     frameRate);
     return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
 }
 
@@ -344,7 +373,7 @@
         return true;
     }
 
-    const nsecs_t period = mRateMap[mIdealPeriod].slope;
+    const nsecs_t period = mRateMap[idealPeriod()].slope;
     const nsecs_t justBeforeTimePoint = timePoint - period / 2;
     const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
     ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
@@ -352,14 +381,127 @@
     return vsyncSequence.seq % divisor == 0;
 }
 
-void VSyncPredictor::setDisplayModeData(const DisplayModeData& displayModeData) {
-    ALOGV("%s %s: RenderRate %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
-          to_string(displayModeData.renderRate).c_str(),
-          displayModeData.notifyExpectedPresentTimeoutOpt
-                  ? std::to_string(displayModeData.notifyExpectedPresentTimeoutOpt->ns()).c_str()
-                  : "N/A");
+void VSyncPredictor::setRenderRate(Fps renderRate) {
+    ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
+    ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
     std::lock_guard lock(mMutex);
-    mDisplayModeDataOpt = displayModeData;
+    mRenderRateOpt = renderRate;
+}
+
+void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
+    LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
+                        "mode does not belong to the display");
+    ATRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
+    const auto timeout = modePtr->getVrrConfig()
+            ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
+            : std::nullopt;
+    ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
+          to_string(*modePtr).c_str(),
+          timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A");
+    std::lock_guard lock(mMutex);
+
+    mDisplayModePtr = modePtr;
+    traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
+
+    static constexpr size_t kSizeLimit = 30;
+    if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
+        mRateMap.erase(mRateMap.begin());
+    }
+
+    if (mRateMap.find(idealPeriod()) == mRateMap.end()) {
+        mRateMap[idealPeriod()] = {idealPeriod(), 0};
+    }
+
+    clearTimestamps();
+}
+
+void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
+                                                  TimePoint lastConfirmedPresentTime) {
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+    const auto minFramePeriod = minFramePeriodLocked().ns();
+
+    auto prev = lastConfirmedPresentTime.ns();
+    for (auto& current : mPastExpectedPresentTimes) {
+        if (CC_UNLIKELY(mTraceOn)) {
+            ATRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
+                                  static_cast<float>(current.ns() - lastConfirmedPresentTime.ns()) /
+                                          1e6f);
+        }
+
+        const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod;
+        if (minPeriodViolation) {
+            ATRACE_NAME("minPeriodViolation");
+            current = TimePoint::fromNs(prev + minFramePeriod);
+            prev = current.ns();
+        } else {
+            break;
+        }
+    }
+
+    if (!mPastExpectedPresentTimes.empty()) {
+        const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
+        if (phase > 0ns) {
+            if (mLastVsyncSequence) {
+                mLastVsyncSequence->vsyncTime += phase.ns();
+            }
+        }
+    }
+}
+
+void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
+                                  TimePoint lastConfirmedPresentTime) {
+    ATRACE_CALL();
+    std::lock_guard lock(mMutex);
+
+    if (!mDisplayModePtr->getVrrConfig()) return;
+
+    if (CC_UNLIKELY(mTraceOn)) {
+        ATRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
+                              static_cast<float>(expectedPresentTime.ns() -
+                                                 lastConfirmedPresentTime.ns()) /
+                                      1e6f);
+    }
+    mPastExpectedPresentTimes.push_back(expectedPresentTime);
+
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+
+    const auto minFramePeriod = minFramePeriodLocked().ns();
+    while (!mPastExpectedPresentTimes.empty()) {
+        const auto front = mPastExpectedPresentTimes.front().ns();
+        const bool frontIsLastConfirmed =
+                std::abs(front - lastConfirmedPresentTime.ns()) < threshold;
+        const bool frontIsBeforeConfirmed =
+                front < lastConfirmedPresentTime.ns() - minFramePeriod + threshold;
+        if (frontIsLastConfirmed || frontIsBeforeConfirmed) {
+            if (CC_UNLIKELY(mTraceOn)) {
+                ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+                                      static_cast<float>(lastConfirmedPresentTime.ns() -
+                                                         mPastExpectedPresentTimes.front().ns()) /
+                                              1e6f);
+            }
+            mPastExpectedPresentTimes.pop_front();
+        } else {
+            break;
+        }
+    }
+
+    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+}
+
+void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
+    ATRACE_CALL();
+
+    std::lock_guard lock(mMutex);
+    if (!mDisplayModePtr->getVrrConfig()) return;
+
+    // We don't know when the frame is going to be presented, so we assume it missed one vsync
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto lastConfirmedPresentTime =
+            TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
+
+    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
@@ -369,26 +511,7 @@
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
-    return mRateMap.find(mIdealPeriod)->second;
-}
-
-void VSyncPredictor::setPeriod(nsecs_t period) {
-    ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str());
-    traceInt64("VSP-setPeriod", period);
-
-    std::lock_guard lock(mMutex);
-    static constexpr size_t kSizeLimit = 30;
-    if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
-        mRateMap.erase(mRateMap.begin());
-    }
-
-    // TODO(b/308610306) mIdealPeriod to be updated with setDisplayModeData
-    mIdealPeriod = period;
-    if (mRateMap.find(period) == mRateMap.end()) {
-        mRateMap[mIdealPeriod] = {period, 0};
-    }
-
-    clearTimestamps();
+    return mRateMap.find(idealPeriod())->second;
 }
 
 void VSyncPredictor::clearTimestamps() {
@@ -412,18 +535,18 @@
 
 void VSyncPredictor::resetModel() {
     std::lock_guard lock(mMutex);
-    mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
+    mRateMap[idealPeriod()] = {idealPeriod(), 0};
     clearTimestamps();
 }
 
 void VSyncPredictor::dump(std::string& result) const {
     std::lock_guard lock(mMutex);
-    StringAppendF(&result, "\tmIdealPeriod=%.2f\n", mIdealPeriod / 1e6f);
+    StringAppendF(&result, "\tmDisplayModePtr=%s\n", to_string(*mDisplayModePtr).c_str());
     StringAppendF(&result, "\tRefresh Rate Map:\n");
-    for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
+    for (const auto& [period, periodInterceptTuple] : mRateMap) {
         StringAppendF(&result,
                       "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
-                      idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
+                      period / 1e6f, periodInterceptTuple.slope / 1e6f,
                       periodInterceptTuple.intercept);
     }
 }
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index c271eb7..72a3431 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <deque>
 #include <mutex>
 #include <unordered_map>
 #include <vector>
@@ -31,14 +32,14 @@
 public:
     /*
      * \param [in] PhysicalDisplayid The display this corresponds to.
-     * \param [in] idealPeriod  The initial ideal period to use.
+     * \param [in] modePtr  The initial display mode
      * \param [in] historySize  The internal amount of entries to store in the model.
      * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
      * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
      */
-    VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize,
+    VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
                    size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
                    IVsyncTrackerCallback&);
     ~VSyncPredictor();
@@ -46,17 +47,9 @@
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
     nsecs_t currentPeriod() const final EXCLUDES(mMutex);
+    Period minFramePeriod() const final EXCLUDES(mMutex);
     void resetModel() final EXCLUDES(mMutex);
 
-    /*
-     * Inform the model that the period is anticipated to change to a new value.
-     * model will use the period parameter to predict vsync events until enough
-     * timestamps with the new period have been collected.
-     *
-     * \param [in] period   The new period that should be used.
-     */
-    void setPeriod(nsecs_t period) final EXCLUDES(mMutex);
-
     /* Query if the model is in need of more samples to make a prediction.
      * \return  True, if model would benefit from more samples, False if not.
      */
@@ -71,7 +64,13 @@
 
     bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
 
-    void setDisplayModeData(const DisplayModeData&) final EXCLUDES(mMutex);
+    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
+
+    void setRenderRate(Fps) final EXCLUDES(mMutex);
+
+    void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
+            EXCLUDES(mMutex);
+    void onFrameMissed(TimePoint expectedPresentTime) final EXCLUDES(mMutex);
 
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
@@ -90,12 +89,15 @@
     Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
     nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
     bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
+    Period minFramePeriodLocked() const REQUIRES(mMutex);
+    void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
 
     struct VsyncSequence {
         nsecs_t vsyncTime;
         int64_t seq;
     };
     VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
+    nsecs_t idealPeriod() const REQUIRES(mMutex);
 
     bool const mTraceOn;
     size_t const kHistorySize;
@@ -104,7 +106,6 @@
     IVsyncTrackerCallback& mVsyncTrackerCallback;
     std::mutex mutable mMutex;
 
-    nsecs_t mIdealPeriod GUARDED_BY(mMutex);
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
 
     // Map between ideal vsync period and the calculated model
@@ -113,9 +114,12 @@
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
-    std::optional<DisplayModeData> mDisplayModeDataOpt GUARDED_BY(mMutex);
+    ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
+    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
 
     mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
+
+    std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 2938aa3..24737e4 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -116,32 +116,34 @@
     }
 }
 
-void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) {
+void VSyncReactor::startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr> modePtr) {
     ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mPeriodConfirmationInProgress = true;
-    mPeriodTransitioningTo = newPeriod;
+    mModePtrTransitioningTo = modePtr.get();
     mMoreSamplesNeeded = true;
     setIgnorePresentFencesInternal(true);
 }
 
 void VSyncReactor::endPeriodTransition() {
     ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
-    mPeriodTransitioningTo.reset();
+    mModePtrTransitioningTo.reset();
     mPeriodConfirmationInProgress = false;
     mLastHwVsync.reset();
 }
 
-void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) {
-    ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(), period);
+void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
+    ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
+                 modePtr->getVsyncRate().getPeriodNsecs());
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) {
+    if (!mSupportKernelIdleTimer &&
+        modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
     } else {
-        startPeriodTransitionInternal(period);
+        startPeriodTransitionInternal(modePtr);
     }
 }
 
@@ -159,14 +161,16 @@
         return false;
     }
 
-    const bool periodIsChanging =
-            mPeriodTransitioningTo && (*mPeriodTransitioningTo != mTracker.currentPeriod());
+    const std::optional<Period> newPeriod = mModePtrTransitioningTo
+            ? mModePtrTransitioningTo->getVsyncRate().getPeriod()
+            : std::optional<Period>{};
+    const bool periodIsChanging = newPeriod && (newPeriod->ns() != mTracker.currentPeriod());
     if (mSupportKernelIdleTimer && !periodIsChanging) {
         // Clear out the Composer-provided period and use the allowance logic below
         HwcVsyncPeriod = {};
     }
 
-    auto const period = mPeriodTransitioningTo ? *mPeriodTransitioningTo : mTracker.currentPeriod();
+    auto const period = newPeriod ? newPeriod->ns() : mTracker.currentPeriod();
     static constexpr int allowancePercent = 10;
     static constexpr std::ratio<allowancePercent, 100> allowancePercentRatio;
     auto const allowance = period * allowancePercentRatio.num / allowancePercentRatio.den;
@@ -185,8 +189,8 @@
     std::lock_guard lock(mMutex);
     if (periodConfirmed(timestamp, hwcVsyncPeriod)) {
         ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
-        if (mPeriodTransitioningTo) {
-            mTracker.setPeriod(*mPeriodTransitioningTo);
+        if (mModePtrTransitioningTo) {
+            mTracker.setDisplayModePtr(ftl::as_non_null(mModePtrTransitioningTo));
             *periodFlushed = true;
         }
 
@@ -228,10 +232,11 @@
                   mInternalIgnoreFences, mExternalIgnoreFences);
     StringAppendF(&result, "mMoreSamplesNeeded=%d mPeriodConfirmationInProgress=%d\n",
                   mMoreSamplesNeeded, mPeriodConfirmationInProgress);
-    if (mPeriodTransitioningTo) {
-        StringAppendF(&result, "mPeriodTransitioningTo=%" PRId64 "\n", *mPeriodTransitioningTo);
+    if (mModePtrTransitioningTo) {
+        StringAppendF(&result, "mModePtrTransitioningTo=%s\n",
+                      to_string(*mModePtrTransitioningTo).c_str());
     } else {
-        StringAppendF(&result, "mPeriodTransitioningTo=nullptr\n");
+        StringAppendF(&result, "mModePtrTransitioningTo=nullptr\n");
     }
 
     if (mLastHwVsync) {
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index f230242..2415a66 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -27,6 +27,7 @@
 
 #include <scheduler/TimeKeeper.h>
 
+#include "VSyncTracker.h"
 #include "VsyncController.h"
 
 namespace android::scheduler {
@@ -45,7 +46,7 @@
     bool addPresentFence(std::shared_ptr<FenceTime>) final;
     void setIgnorePresentFences(bool ignore) final;
 
-    void startPeriodTransition(nsecs_t period, bool force) final;
+    void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, bool force) final;
 
     bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
                              bool* periodFlushed) final;
@@ -57,7 +58,7 @@
 private:
     void setIgnorePresentFencesInternal(bool ignore) REQUIRES(mMutex);
     void updateIgnorePresentFencesInternal() REQUIRES(mMutex);
-    void startPeriodTransitionInternal(nsecs_t newPeriod) REQUIRES(mMutex);
+    void startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr>) REQUIRES(mMutex);
     void endPeriodTransition() REQUIRES(mMutex);
     bool periodConfirmed(nsecs_t vsync_timestamp, std::optional<nsecs_t> hwcVsyncPeriod)
             REQUIRES(mMutex);
@@ -74,7 +75,7 @@
 
     bool mMoreSamplesNeeded GUARDED_BY(mMutex) = false;
     bool mPeriodConfirmationInProgress GUARDED_BY(mMutex) = false;
-    std::optional<nsecs_t> mPeriodTransitioningTo GUARDED_BY(mMutex);
+    DisplayModePtr mModePtrTransitioningTo GUARDED_BY(mMutex);
     std::optional<nsecs_t> mLastHwVsync GUARDED_BY(mMutex);
 
     hal::PowerMode mDisplayPowerMode GUARDED_BY(mMutex) = hal::PowerMode::ON;
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 7eedc31..1ed863c 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -20,25 +20,16 @@
 #include <utils/Timers.h>
 
 #include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
 
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
 
-struct DisplayModeData {
-    Fps renderRate;
-    std::optional<Period> notifyExpectedPresentTimeoutOpt;
-
-    bool operator==(const DisplayModeData& other) const {
-        return isApproxEqual(renderRate, other.renderRate) &&
-                notifyExpectedPresentTimeoutOpt == other.notifyExpectedPresentTimeoutOpt;
-    }
-};
-
 struct IVsyncTrackerCallback {
     virtual ~IVsyncTrackerCallback() = default;
-    virtual void onVsyncGenerated(PhysicalDisplayId, TimePoint expectedPresentTime,
-                                  const DisplayModeData&, Period vsyncPeriod) = 0;
+    virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                                  Fps renderRate) = 0;
 };
 
 /*
@@ -78,11 +69,9 @@
     virtual nsecs_t currentPeriod() const = 0;
 
     /*
-     * Inform the tracker that the period is changing and the tracker needs to recalibrate itself.
-     *
-     * \param [in] period   The period that the system is changing into.
+     * The minimal period frames can be displayed.
      */
-    virtual void setPeriod(nsecs_t period) = 0;
+    virtual Period minFramePeriod() const = 0;
 
     /* Inform the tracker that the samples it has are not accurate for prediction. */
     virtual void resetModel() = 0;
@@ -98,20 +87,30 @@
     virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
     /*
-     * Sets the metadata about the currently active display mode such as VRR
-     * timeout period, vsyncPeriod and framework property such as render rate.
-     * If the render rate is not a divisor of the period, the render rate is
-     * ignored until the period changes.
+     * Sets the active mode of the display which includes the vsync period and other VRR attributes.
+     * This will inform the tracker that the period is changing and the tracker needs to recalibrate
+     * itself.
+     *
+     * \param [in] DisplayModePtr The display mode the tracker will use.
+     */
+    virtual void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) = 0;
+
+    /*
+     * Sets a render rate on the tracker. If the render rate is not a divisor
+     * of the period, the render rate is ignored until the period changes.
      * The tracker will continue to track the vsync timeline and expect it
      * to match the current period, however, nextAnticipatedVSyncTimeFrom will
      * return vsyncs according to the render rate set. Setting a render rate is useful
      * when a display is running at 120Hz but the render frame rate is 60Hz.
-     * When IVsyncTrackerCallback::onVsyncGenerated callback is made we will pass along
-     * the vsyncPeriod, render rate and timeoutNs.
      *
-     * \param [in] DisplayModeData The DisplayModeData the tracker will use.
+     * \param [in] Fps   The render rate the tracker should operate at.
      */
-    virtual void setDisplayModeData(const DisplayModeData&) = 0;
+    virtual void setRenderRate(Fps) = 0;
+
+    virtual void onFrameBegin(TimePoint expectedPresentTime,
+                              TimePoint lastConfirmedPresentTime) = 0;
+
+    virtual void onFrameMissed(TimePoint expectedPresentTime) = 0;
 
     virtual void dump(std::string& result) const = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h
index 9177899..807a7fb 100644
--- a/services/surfaceflinger/Scheduler/VsyncController.h
+++ b/services/surfaceflinger/Scheduler/VsyncController.h
@@ -22,6 +22,7 @@
 
 #include <DisplayHardware/HWComposer.h>
 #include <DisplayHardware/Hal.h>
+#include <scheduler/FrameRateMode.h>
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
@@ -59,13 +60,14 @@
                                      bool* periodFlushed) = 0;
 
     /*
-     * Inform the controller that the period is changing and the controller needs to recalibrate
-     * itself. The controller will end the period transition internally.
+     * Inform the controller that the display mode is changing and the controller needs to
+     * recalibrate itself to the new vsync period. The controller will end the period transition
+     * internally.
      *
-     * \param [in] period   The period that the system is changing into.
+     * \param [in] DisplayModePtr  The new mode the display is changing to.
      * \param [in] force    True to recalibrate even if period matches the existing period.
      */
-    virtual void startPeriodTransition(nsecs_t period, bool force) = 0;
+    virtual void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, bool force) = 0;
 
     /*
      * Tells the tracker to stop using present fences to get a vsync signal.
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 5fb53f9..ff1c9e9 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -16,6 +16,8 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/FlagManager.h>
+
 #include <ftl/fake_guard.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
@@ -53,14 +55,14 @@
     VSyncCallbackRegistration mRegistration;
 };
 
-VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features,
+VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
                              RequestHardwareVsync requestHardwareVsync,
                              IVsyncTrackerCallback& callback)
-      : mId(id),
+      : mId(modePtr->getPhysicalDisplayId()),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(id, callback)),
+        mTracker(createTracker(modePtr, callback)),
         mDispatch(createDispatch(mTracker)),
-        mController(createController(id, *mTracker, features)),
+        mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
                         ? std::make_unique<PredictedVsyncTracer>(mDispatch)
                         : nullptr) {}
@@ -79,6 +81,13 @@
     return Period::fromNs(mTracker->currentPeriod());
 }
 
+Period VsyncSchedule::minFramePeriod() const {
+    if (FlagManager::getInstance().vrr_config()) {
+        return mTracker->minFramePeriod();
+    }
+    return period();
+}
+
 TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const {
     return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns()));
 }
@@ -101,17 +110,15 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id,
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr,
                                                        IVsyncTrackerCallback& callback) {
     // TODO(b/144707443): Tune constants.
-    constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs();
     constexpr size_t kHistorySize = 20;
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
-    return std::make_unique<VSyncPredictor>(id, kInitialPeriod, kHistorySize,
-                                            kMinSamplesForPrediction, kDiscardOutlierPercent,
-                                            callback);
+    return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
+                                            kDiscardOutlierPercent, callback);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
@@ -140,9 +147,9 @@
     return reactor;
 }
 
-void VsyncSchedule::startPeriodTransition(Period period, bool force) {
+void VsyncSchedule::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
-    mController->startPeriodTransition(period.ns(), force);
+    mController->onDisplayModeChanged(modePtr, force);
     enableHardwareVsyncLocked();
 }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index ca61f87..722ea0b 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -57,21 +57,23 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(PhysicalDisplayId, FeatureFlags, RequestHardwareVsync, IVsyncTrackerCallback&);
+    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync,
+                  IVsyncTrackerCallback&);
     ~VsyncSchedule();
 
     // IVsyncSource overrides:
     Period period() const override;
     TimePoint vsyncDeadlineAfter(TimePoint) const override;
+    Period minFramePeriod() const override;
 
-    // Inform the schedule that the period is changing and the schedule needs to recalibrate
-    // itself. The schedule will end the period transition internally. This will
-    // enable hardware VSYNCs in order to calibrate.
+    // Inform the schedule that the display mode changed the schedule needs to recalibrate
+    // itself to the new vsync period. The schedule will end the period transition internally.
+    // This will enable hardware VSYNCs in order to calibrate.
     //
-    // \param [in] period   The period that the system is changing into.
+    // \param [in] DisplayModePtr  The mode that the display is changing to.
     // \param [in] force    True to force a transition even if it is not a
     //                      change.
-    void startPeriodTransition(Period period, bool force);
+    void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, bool force);
 
     // Pass a VSYNC sample to VsyncController. Return true if
     // VsyncController detected that the VSYNC period changed. Enable or disable
@@ -125,7 +127,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(PhysicalDisplayId, IVsyncTrackerCallback&);
+    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&);
     static DispatchPtr createDispatch(TrackerPtr);
     static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index ae74205..70d4846 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -50,11 +50,11 @@
     TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
 
     // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
-    TimePoint pastVsyncTime(Period vsyncPeriod) const;
+    TimePoint pastVsyncTime(Period minFramePeriod) const;
 
     // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
-    TimePoint previousFrameVsyncTime(Period vsyncPeriod) const {
-        return mExpectedPresentTime - vsyncPeriod;
+    TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
+        return mExpectedPresentTime - minFramePeriod;
     }
 
     // The present fence for the frame that had targeted the most recent VSYNC before this frame.
@@ -62,14 +62,14 @@
     // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
     // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
     // signaled by now (unless that frame missed).
-    const FenceTimePtr& presentFenceForPastVsync(Period vsyncPeriod) const;
+    const FenceTimePtr& presentFenceForPastVsync(Period minFramePeriod) const;
 
     // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
     const FenceTimePtr& presentFenceForPreviousFrame() const {
         return mPresentFences.front().fenceTime;
     }
 
-    bool wouldPresentEarly(Period vsyncPeriod) const;
+    bool wouldPresentEarly(Period minFramePeriod) const;
 
     bool isFramePending() const { return mFramePending; }
     bool didMissFrame() const { return mFrameMissed; }
@@ -96,9 +96,9 @@
 
 private:
     template <int N>
-    inline bool targetsVsyncsAhead(Period vsyncPeriod) const {
+    inline bool targetsVsyncsAhead(Period minFramePeriod) const {
         static_assert(N > 1);
-        return expectedFrameDuration() > (N - 1) * vsyncPeriod;
+        return expectedFrameDuration() > (N - 1) * minFramePeriod;
     }
 };
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
index bb2de75..0154060 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -23,6 +23,7 @@
 struct IVsyncSource {
     virtual Period period() const = 0;
     virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+    virtual Period minFramePeriod() const = 0;
 
 protected:
     ~IVsyncSource() = default;
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 7a18654..e80372b 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -27,28 +27,28 @@
         mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
         mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
 
-TimePoint FrameTarget::pastVsyncTime(Period vsyncPeriod) const {
+TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const {
     // TODO(b/267315508): Generalize to N VSYNCs.
-    const int shift = static_cast<int>(targetsVsyncsAhead<2>(vsyncPeriod));
-    return mExpectedPresentTime - Period::fromNs(vsyncPeriod.ns() << shift);
+    const int shift = static_cast<int>(targetsVsyncsAhead<2>(minFramePeriod));
+    return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
 }
 
-const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period vsyncPeriod) const {
+const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
     // TODO(b/267315508): Generalize to N VSYNCs.
-    const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(vsyncPeriod));
+    const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
     return mPresentFences[i].fenceTime;
 }
 
-bool FrameTarget::wouldPresentEarly(Period vsyncPeriod) const {
+bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const {
     // TODO(b/241285475): Since this is called during `composite`, the calls to `targetsVsyncsAhead`
     // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
 
     // TODO(b/267315508): Generalize to N VSYNCs.
-    if (targetsVsyncsAhead<3>(vsyncPeriod)) {
+    if (targetsVsyncsAhead<3>(minFramePeriod)) {
         return true;
     }
 
-    const auto fence = presentFenceForPastVsync(vsyncPeriod);
+    const auto fence = presentFenceForPastVsync(minFramePeriod);
     return fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
 }
 
@@ -68,6 +68,7 @@
     mScheduledPresentTime = args.expectedVsyncTime;
 
     const Period vsyncPeriod = vsyncSource.period();
+    const Period minFramePeriod = vsyncSource.minFramePeriod();
 
     // Calculate the expected present time once and use the cached value throughout this frame to
     // make sure all layers are seeing this same value.
@@ -85,7 +86,7 @@
                   ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
                   mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
 
-    const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(vsyncPeriod);
+    const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(minFramePeriod);
 
     // In cases where the present fence is about to fire, give it a small grace period instead of
     // giving up on the frame.
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 1e038d1..c883385 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -17,23 +17,29 @@
 #include <ftl/optional.h>
 #include <gtest/gtest.h>
 
+#include <common/test/FlagUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
 namespace {
 
 struct VsyncSource final : IVsyncSource {
-    VsyncSource(Period period, TimePoint deadline) : vsyncPeriod(period), vsyncDeadline(deadline) {}
+    VsyncSource(Period period, Period minFramePeriod, TimePoint deadline)
+          : vsyncPeriod(period), framePeriod(minFramePeriod), vsyncDeadline(deadline) {}
 
     const Period vsyncPeriod;
+    const Period framePeriod;
     const TimePoint vsyncDeadline;
 
     Period period() const override { return vsyncPeriod; }
     TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+    Period minFramePeriod() const override { return framePeriod; }
 };
 
 } // namespace
@@ -44,10 +50,13 @@
 
     struct Frame {
         Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
-              Duration frameDuration, Fps refreshRate,
+              Duration frameDuration, Fps refreshRate, Fps peakRefreshRate,
               FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled,
               const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt)
-              : testPtr(testPtr), frameBeginTime(frameBeginTime), period(refreshRate.getPeriod()) {
+              : testPtr(testPtr),
+                frameBeginTime(frameBeginTime),
+                period(refreshRate.getPeriod()),
+                minFramePeriod(peakRefreshRate.getPeriod()) {
             const FrameTargeter::BeginFrameArgs args{.frameBeginTime = frameBeginTime,
                                                      .vsyncId = vsyncId,
                                                      .expectedVsyncTime =
@@ -58,7 +67,7 @@
                                           vsyncSourceOpt
                                                   .or_else([&] {
                                                       return std::make_optional(
-                                                              VsyncSource(period,
+                                                              VsyncSource(period, period,
                                                                           args.expectedVsyncTime));
                                                   })
                                                   .value(),
@@ -88,6 +97,7 @@
 
         TimePoint& frameBeginTime;
         const Period period;
+        const Period minFramePeriod;
 
         bool ended = false;
     };
@@ -103,7 +113,7 @@
     VsyncId vsyncId{42};
     {
         TimePoint frameBeginTime(989ms);
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, 60_Hz);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, 60_Hz, 60_Hz);
 
         EXPECT_EQ(target().vsyncId(), VsyncId{42});
         EXPECT_EQ(target().frameBeginTime(), TimePoint(989ms));
@@ -112,7 +122,7 @@
     }
     {
         TimePoint frameBeginTime(1100ms);
-        const Frame frame(this, vsyncId++, frameBeginTime, 11ms, 60_Hz);
+        const Frame frame(this, vsyncId++, frameBeginTime, 11ms, 60_Hz, 60_Hz);
 
         EXPECT_EQ(target().vsyncId(), VsyncId{43});
         EXPECT_EQ(target().frameBeginTime(), TimePoint(1100ms));
@@ -127,9 +137,10 @@
     TimePoint frameBeginTime(777ms);
 
     constexpr Fps kRefreshRate = 120_Hz;
-    const VsyncSource vsyncSource(kRefreshRate.getPeriod(), frameBeginTime + 5ms);
+    const VsyncSource vsyncSource(kRefreshRate.getPeriod(), kRefreshRate.getPeriod(),
+                                  frameBeginTime + 5ms);
     const Frame frame(this, VsyncId{123}, frameBeginTime, kFrameDuration, kRefreshRate,
-                      Frame::fenceSignaled, vsyncSource);
+                      kRefreshRate, Frame::fenceSignaled, vsyncSource);
 
     EXPECT_EQ(target().expectedPresentTime(), vsyncSource.vsyncDeadline + vsyncSource.vsyncPeriod);
 }
@@ -142,7 +153,7 @@
     constexpr Duration kFrameDuration = 13ms;
 
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate);
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
         const auto fence = frame.end();
 
         EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
@@ -160,7 +171,31 @@
     FenceTimePtr previousFence = FenceTime::NO_FENCE;
 
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate);
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
+        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
+
+        previousFence = fence;
+    }
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Fps kPeakRefreshRate = 240_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+                    kPeakRefreshRate);
         const auto fence = frame.end();
 
         EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
@@ -184,12 +219,12 @@
 
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
     }
 
     // The target is early if the past present fence was signaled.
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
     const auto fence = frame.end();
     fence->signalForTest(frameBeginTime.ns());
 
@@ -204,18 +239,18 @@
 
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
     }
 
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
     const auto fence = frame.end();
     fence->signalForTest(frameBeginTime.ns());
 
     // The target is two VSYNCs ahead, so the past present fence is still pending.
     EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
 
-    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate); }
+    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
 
     // The target is early if the past present fence was signaled.
     EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
@@ -226,7 +261,7 @@
     constexpr Fps kRefreshRate = 144_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
 
-    const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate);
+    const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
 
     // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
     EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
@@ -243,7 +278,7 @@
     EXPECT_FALSE(target().didMissHwcFrame());
 
     {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().isFramePending());
 
         // The frame did not miss if the past present fence is invalid.
@@ -251,7 +286,8 @@
         EXPECT_FALSE(target().didMissHwcFrame());
     }
     {
-        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, Frame::fencePending);
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
+                    Frame::fencePending);
         EXPECT_TRUE(target().isFramePending());
 
         // The frame missed if the past present fence is pending.
@@ -261,7 +297,8 @@
         frame.end(CompositionCoverage::Gpu);
     }
     {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, Frame::fencePending);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
+                          Frame::fencePending);
         EXPECT_TRUE(target().isFramePending());
 
         // The GPU frame missed if the past present fence is pending.
@@ -269,7 +306,7 @@
         EXPECT_FALSE(target().didMissHwcFrame());
     }
     {
-        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().isFramePending());
 
         const auto fence = frame.end();
@@ -277,7 +314,7 @@
         fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2 + 1);
     }
     {
-        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().isFramePending());
 
         const auto fence = frame.end();
@@ -289,7 +326,7 @@
         EXPECT_TRUE(target().didMissHwcFrame());
     }
     {
-        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
         EXPECT_FALSE(target().isFramePending());
 
         // The frame did not miss if the past present fence was signaled within slop.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 644b6ef..6e6229a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1177,7 +1177,7 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) {
+void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) {
     const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
@@ -1190,10 +1190,9 @@
     const auto mode = request.mode;
     const bool emitEvent = request.emitEvent;
 
-    switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)),
-                                          force)) {
-        case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
-            // Set the render rate as setDesiredActiveMode updated it.
+    switch (display->setDesiredMode(DisplayDevice::ActiveModeInfo(std::move(request)), force)) {
+        case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
+            // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
             mScheduler->setRenderRate(displayId,
                                       display->refreshRateSelector().getActiveMode().fps);
 
@@ -1203,7 +1202,7 @@
             // Start receiving vsync samples now, so that we can detect a period
             // switch.
             mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */,
-                                              mode.modePtr->getVsyncRate());
+                                              mode.modePtr.get());
 
             // As we called to set period, we will call to onRefreshRateChangeCompleted once
             // VsyncController model is locked.
@@ -1215,7 +1214,7 @@
 
             mScheduler->setModeChangePending(true);
             break;
-        case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch:
+        case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
             mScheduler->setRenderRate(displayId, mode.fps);
 
             if (displayId == mActiveDisplayId) {
@@ -1227,7 +1226,7 @@
                 dispatchDisplayModeChangeEvent(displayId, mode);
             }
             break;
-        case DisplayDevice::DesiredActiveModeAction::None:
+        case DisplayDevice::DesiredModeAction::None:
             break;
     }
 }
@@ -1287,27 +1286,27 @@
     const auto displayId = display.getPhysicalId();
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
-    const auto upcomingModeInfo = display.getUpcomingActiveMode();
-    if (!upcomingModeInfo.modeOpt) {
+    const auto pendingMode = display.getPendingMode();
+    if (!pendingMode.modeOpt) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
         return;
     }
 
     if (display.getActiveMode().modePtr->getResolution() !=
-        upcomingModeInfo.modeOpt->modePtr->getResolution()) {
+        pendingMode.modeOpt->modePtr->getResolution()) {
         auto& state = mCurrentState.displays.editValueFor(display.getDisplayToken());
         // We need to generate new sequenceId in order to recreate the display (and this
         // way the framebuffer).
         state.sequenceId = DisplayDeviceState{}.sequenceId;
-        state.physical->activeMode = upcomingModeInfo.modeOpt->modePtr.get();
+        state.physical->activeMode = pendingMode.modeOpt->modePtr.get();
         processDisplayChangesLocked();
 
         // processDisplayChangesLocked will update all necessary components so we're done here.
         return;
     }
 
-    const auto& activeMode = *upcomingModeInfo.modeOpt;
+    const auto& activeMode = *pendingMode.modeOpt;
     display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
                                activeMode.fps);
 
@@ -1316,27 +1315,26 @@
         updatePhaseConfiguration(activeMode.fps);
     }
 
-    if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
+    if (pendingMode.event != scheduler::DisplayModeEvent::None) {
         dispatchDisplayModeChangeEvent(displayId, activeMode);
     }
 }
 
-void SurfaceFlinger::clearDesiredActiveModeState(const sp<DisplayDevice>& display) {
-    display->clearDesiredActiveModeState();
+void SurfaceFlinger::dropModeRequest(const sp<DisplayDevice>& display) {
+    display->clearDesiredMode();
     if (display->getPhysicalId() == mActiveDisplayId) {
         // TODO(b/255635711): Check for pending mode changes on other displays.
         mScheduler->setModeChangePending(false);
     }
 }
 
-void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) {
-    const auto desiredActiveMode = display->getDesiredActiveMode();
-    const auto& modeOpt = desiredActiveMode->modeOpt;
+void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) {
+    const auto desiredModeOpt = display->getDesiredMode();
+    const auto& modeOpt = desiredModeOpt->modeOpt;
     const auto displayId = modeOpt->modePtr->getPhysicalDisplayId();
-    const auto vsyncRate = modeOpt->modePtr->getVsyncRate();
     const auto renderFps = modeOpt->fps;
-    clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, vsyncRate);
+    dropModeRequest(display);
+    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, modeOpt->modePtr.get());
     mScheduler->setRenderRate(displayId, renderFps);
 
     if (displayId == mActiveDisplayId) {
@@ -1353,25 +1351,23 @@
         const auto display = getDisplayDeviceLocked(id);
         if (!display) continue;
 
-        // Store the local variable to release the lock.
-        const auto desiredActiveMode = display->getDesiredActiveMode();
-        if (!desiredActiveMode) {
-            // No desired active mode pending to be applied.
+        const auto desiredModeOpt = display->getDesiredMode();
+        if (!desiredModeOpt) {
             continue;
         }
 
         if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
-            clearDesiredActiveModeState(display);
+            dropModeRequest(display);
             continue;
         }
 
-        const auto desiredModeId = desiredActiveMode->modeOpt->modePtr->getId();
+        const auto desiredModeId = desiredModeOpt->modeOpt->modePtr->getId();
         const auto displayModePtrOpt = physical.snapshot().displayModes().get(desiredModeId);
 
         if (!displayModePtrOpt) {
             ALOGW("Desired display mode is no longer supported. Mode ID = %d",
                   desiredModeId.value());
-            clearDesiredActiveModeState(display);
+            dropModeRequest(display);
             continue;
         }
 
@@ -1379,9 +1375,8 @@
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
-        if (display->getActiveMode() == desiredActiveMode->modeOpt) {
-            // we are already in the requested mode, there is nothing left to do
-            desiredActiveModeChangeDone(display);
+        if (display->getActiveMode() == desiredModeOpt->modeOpt) {
+            applyActiveMode(display);
             continue;
         }
 
@@ -1389,9 +1384,9 @@
         // allowed modes might have changed by the time we process the refresh.
         // Make sure the desired mode is still allowed
         const auto displayModeAllowed =
-                display->refreshRateSelector().isModeAllowed(*desiredActiveMode->modeOpt);
+                display->refreshRateSelector().isModeAllowed(*desiredModeOpt->modeOpt);
         if (!displayModeAllowed) {
-            clearDesiredActiveModeState(display);
+            dropModeRequest(display);
             continue;
         }
 
@@ -1401,13 +1396,7 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        const auto status =
-                display->initiateModeChange(*desiredActiveMode, constraints, &outTimeline);
-
-        if (status != NO_ERROR) {
-            // initiateModeChange may fail if a hotplug event is just about
-            // to be sent. We just log the error in this case.
-            ALOGW("initiateModeChange failed: %d", status);
+        if (!display->initiateModeChange(*desiredModeOpt, constraints, outTimeline)) {
             continue;
         }
 
@@ -1428,9 +1417,9 @@
         const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
         finalizeDisplayModeChange(*display);
 
-        const auto desiredActiveMode = display->getDesiredActiveMode();
-        if (desiredActiveMode && display->getActiveMode() == desiredActiveMode->modeOpt) {
-            desiredActiveModeChangeDone(display);
+        const auto desiredModeOpt = display->getDesiredMode();
+        if (desiredModeOpt && display->getActiveMode() == desiredModeOpt->modeOpt) {
+            applyActiveMode(display);
         }
     }
 }
@@ -2487,6 +2476,10 @@
 
     if (pacesetterFrameTarget.isFramePending()) {
         if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
+            if (FlagManager::getInstance().vrr_config()) {
+                mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
+                        pacesetterFrameTarget.expectedPresentTime());
+            }
             scheduleCommit(FrameHint::kNone);
             return false;
         }
@@ -2682,16 +2675,16 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
+    const Period minFramePeriod = mScheduler->getVsyncSchedule()->minFramePeriod();
 
     if (!getHwComposer().getComposer()->isSupported(
                 Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-        pacesetterTarget.wouldPresentEarly(vsyncPeriod)) {
+        pacesetterTarget.wouldPresentEarly(minFramePeriod)) {
         const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
 
         // TODO(b/255601557): Calculate and pass per-display values for each FrameTarget.
         refreshArgs.earliestPresentTime =
-                pacesetterTarget.previousFrameVsyncTime(vsyncPeriod) - hwcMinWorkDuration;
+                pacesetterTarget.previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
     }
 
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
@@ -4016,7 +4009,7 @@
         }
 
         if (display->refreshRateSelector().isModeAllowed(request.mode)) {
-            setDesiredActiveMode(std::move(request));
+            setDesiredMode(std::move(request));
         } else {
             ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
                   to_string(displayId).c_str());
@@ -4045,15 +4038,23 @@
     }
 }
 
-void SurfaceFlinger::onVsyncGenerated(PhysicalDisplayId displayId, TimePoint expectedPresentTime,
-                                      const scheduler::DisplayModeData& displayModeData,
-                                      Period vsyncPeriod) {
-    const auto status =
-            getHwComposer()
-                    .notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
-                                                     displayModeData.renderRate,
-                                                     displayModeData
-                                                             .notifyExpectedPresentTimeoutOpt);
+void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
+                                      ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+    const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
+    const auto timeout = [&]() -> std::optional<Period> {
+        const auto vrrConfig = modePtr->getVrrConfig();
+        if (!vrrConfig) return std::nullopt;
+
+        const auto notifyExpectedPresentConfig =
+                modePtr->getVrrConfig()->notifyExpectedPresentConfig;
+        if (!notifyExpectedPresentConfig) return std::nullopt;
+        return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
+    }();
+
+    const auto displayId = modePtr->getPhysicalDisplayId();
+    const auto status = getHwComposer().notifyExpectedPresentIfRequired(displayId, vsyncPeriod,
+                                                                        expectedPresentTime,
+                                                                        renderRate, timeout);
     if (status != NO_ERROR) {
         ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, __func__,
               displayId.value);
@@ -4706,7 +4707,7 @@
         return false;
     }
 
-    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2;
+    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->minFramePeriod() / 2;
 
     return predictedPresentTime >= expectedPresentTime &&
             predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
@@ -5775,7 +5776,7 @@
 
     display->setPowerMode(mode);
 
-    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getVsyncRate();
+    const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr;
     if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
         // Turn on the display
 
@@ -5812,7 +5813,7 @@
             mScheduler->enableSyntheticVsync(false);
 
             constexpr bool kAllowToEnable = true;
-            mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, refreshRate);
+            mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, activeMode.get());
         }
 
         mVisibleRegionsDirty = true;
@@ -5854,7 +5855,8 @@
             mVisibleRegionsDirty = true;
             scheduleRepaint();
             mScheduler->enableSyntheticVsync(false);
-            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate);
+            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */,
+                                              activeMode.get());
         }
     } else if (mode == hal::PowerMode::DOZE_SUSPEND) {
         // Leave display going to doze
@@ -7283,15 +7285,14 @@
         }
         if (!display->isRefreshRateOverlayEnabled()) return;
 
-        const auto desiredActiveMode = display->getDesiredActiveMode();
-        const std::optional<DisplayModeId> desiredModeId = desiredActiveMode
-                ? std::make_optional(desiredActiveMode->modeOpt->modePtr->getId())
-
-                : std::nullopt;
+        const auto desiredModeIdOpt =
+                display->getDesiredMode().transform([](const DisplayDevice::ActiveModeInfo& info) {
+                    return info.modeOpt->modePtr->getId();
+                });
 
         const bool timerExpired = mKernelIdleTimerEnabled && expired;
 
-        if (display->onKernelTimerChanged(desiredModeId, timerExpired)) {
+        if (display->onKernelTimerChanged(desiredModeIdOpt, timerExpired)) {
             mScheduler->scheduleFrame();
         }
     }));
@@ -8239,18 +8240,19 @@
     auto preferredMode = std::move(*preferredModeOpt);
     const auto preferredModeId = preferredMode.modePtr->getId();
 
+    const Fps preferredFps = preferredMode.fps;
     ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(),
-          to_string(preferredMode.fps).c_str());
+          to_string(preferredFps).c_str());
 
     if (!selector.isModeAllowed(preferredMode)) {
         ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value());
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({preferredMode, .emitEvent = true}, force);
+    setDesiredMode({std::move(preferredMode), .emitEvent = true}, force);
 
     // Update the frameRateOverride list as the display render rate might have changed
-    if (mScheduler->updateFrameRateOverrides(/*consideredSignals*/ {}, preferredMode.fps)) {
+    if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
         triggerOnFrameRateOverridesChanged();
     }
 
@@ -8587,7 +8589,7 @@
                                                   const DisplayDevice& activeDisplay) {
     ATRACE_CALL();
 
-    // For the first display activated during boot, there is no need to force setDesiredActiveMode,
+    // For the first display activated during boot, there is no need to force setDesiredMode,
     // because DM is about to send its policy via setDesiredDisplayModeSpecs.
     bool forceApplyPolicy = false;
 
@@ -8611,9 +8613,9 @@
     sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
 
     // The policy of the new active/pacesetter display may have changed while it was inactive. In
-    // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In
-    // either case, the Scheduler's cachedModeChangedParams must be initialized to the newly active
-    // mode, and the kernel idle timer of the newly active display must be toggled.
+    // that case, its preferred mode has not been propagated to HWC (via setDesiredMode). In either
+    // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
+    // and the kernel idle timer of the newly active display must be toggled.
     applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
                                    forceApplyPolicy);
 }
@@ -8958,6 +8960,10 @@
                      .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap(),
                      .skipRoundCornersWhenProtected =
                              !getRenderEngine().supportsProtectedContent()};
+        // The layer may not exist if it was just created and a screenshot was requested immediately
+        // after. In this case, the hierarchy will be empty so we will not render any layers.
+        args.rootSnapshot.isSecure = mLayerLifecycleManager.getLayerFromId(rootLayerId) &&
+                mLayerLifecycleManager.isLayerSecure(rootLayerId);
         mLayerSnapshotBuilder.update(args);
 
         auto getLayerSnapshotsFn =
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9e6da3f..e90f8fe 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -658,8 +658,8 @@
     void notifyCpuLoadUp() override;
 
     // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(PhysicalDisplayId, TimePoint expectedPresentTime,
-                          const scheduler::DisplayModeData&, Period vsyncPeriod) override;
+    void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                          Fps renderRate) override;
 
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
@@ -686,8 +686,7 @@
     // Show hdr sdr ratio overlay
     bool mHdrSdrRatioOverlay = false;
 
-    void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
-            REQUIRES(mStateLock);
+    void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock);
 
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
@@ -695,9 +694,10 @@
     void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
     void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
 
-    void clearDesiredActiveModeState(const sp<DisplayDevice>&) REQUIRES(mStateLock);
-    // Called when active mode is no longer is progress
-    void desiredActiveModeChangeDone(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+    // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler.
+    void dropModeRequest(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+    void applyActiveMode(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+
     // Called on the main thread in response to setPowerMode()
     void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
             REQUIRES(mStateLock, kMainThreadContext);
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 8da7d8e..e2a1498 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -120,6 +120,7 @@
     DUMP_READ_ONLY_FLAG(hotplug2);
     DUMP_READ_ONLY_FLAG(hdcp_level_hal);
     DUMP_READ_ONLY_FLAG(multithreaded_present);
+    DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -188,6 +189,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
 FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "")
 FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
+FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index e3e4f80..9aabbb9 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -59,6 +59,7 @@
     bool hotplug2() const;
     bool hdcp_level_hal() const;
     bool multithreaded_present() const;
+    bool add_sf_skipped_frames_to_trace() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/tests/unittests/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
similarity index 100%
rename from services/surfaceflinger/tests/unittests/FlagUtils.h
rename to services/surfaceflinger/common/include/common/test/FlagUtils.h
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 9b2d453..4fc39cc 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -806,8 +806,7 @@
     void onChoreographerAttached() override {}
 
     // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
-                          Period) override {}
+    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 8fcfd81..b690d8d 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -28,6 +28,7 @@
 #include "Scheduler/VSyncPredictor.h"
 #include "Scheduler/VSyncReactor.h"
 
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 
@@ -179,8 +180,7 @@
 }
 
 struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
-    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
-                          Period) override {}
+    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 };
 
 void SchedulerFuzzer::fuzzVSyncPredictor() {
@@ -189,14 +189,14 @@
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
     VsyncTrackerCallback callback;
-    scheduler::VSyncPredictor tracker{kDisplayId,
-                                      idealPeriod,
-                                      historySize,
-                                      minimumSamplesForPrediction,
+    const auto mode = ftl::as_non_null(
+            mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod)));
+    scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction,
                                       mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
                                       callback};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
-    tracker.setPeriod(period);
+    tracker.setDisplayModePtr(ftl::as_non_null(
+            mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period))));
     for (uint16_t i = 0; i < minimumSamplesForPrediction; ++i) {
         if (!tracker.needsMoreSamples()) {
             break;
@@ -271,7 +271,10 @@
                                     *vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/,
                                     false);
 
-    reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeBool());
+    const auto mode = ftl::as_non_null(
+            mock::createDisplayMode(DisplayModeId(0),
+                                    Fps::fromPeriodNsecs(mFdp.ConsumeIntegral<nsecs_t>())));
+    reactor.onDisplayModeChanged(mode, mFdp.ConsumeBool());
     bool periodFlushed = false; // Value does not matter, since this is an out
                                 // param from addHwVsyncTimestamp.
     reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed);
@@ -430,6 +433,7 @@
 
         Period period() const { return getFuzzedDuration(fuzzer); }
         TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+        Period minFramePeriod() const { return period(); }
     } vsyncSource{mFdp};
 
     int i = 10;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index 728708f..fa307e9 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -89,8 +89,7 @@
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
 
     nsecs_t currentPeriod() const override { return 1; }
-
-    void setPeriod(nsecs_t /* period */) override {}
+    Period minFramePeriod() const override { return Period::fromNs(currentPeriod()); }
 
     void resetModel() override {}
 
@@ -100,7 +99,7 @@
         return true;
     }
 
-    void setDisplayModeData(const scheduler::DisplayModeData&) override {}
+    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) override {}
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
@@ -109,6 +108,12 @@
         return (timePoint - (timePoint % mPeriod) + mPeriod);
     }
 
+    void setRenderRate(Fps) override {}
+
+    void onFrameBegin(TimePoint, TimePoint) override {}
+
+    void onFrameMissed(TimePoint) override {}
+
     void dump(std::string& /* result */) const override {}
 
 protected:
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index bb3c94a..71c59b2 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -71,6 +71,14 @@
 }
 
 flag {
+  name: "add_sf_skipped_frames_to_trace"
+  namespace: "core_graphics"
+  description: "Add SurfaceFlinger dropped Frames to frame timeline"
+  bug: "273701290"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "refresh_rate_overlay_on_external_display"
   namespace: "core_graphics"
   description: "enable refresh rate indicator on the external display"
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 79864e0..061e121 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -154,6 +154,133 @@
     sc.expectColor(Rect(0, 0, 10, 10), Color::BLUE);
 }
 
+/**
+ * If a parent layer sets the secure flag, but the screenshot requests is for the child hierarchy,
+ * we need to ensure the secure flag is respected from the parent even though the parent isn't
+ * in the captured sub-hierarchy
+ */
+TEST_F(ScreenCaptureTest, CaptureChildRespectsParentSecureFlag) {
+    Rect size(0, 0, 100, 100);
+    Transaction().hide(mBGSurfaceControl).hide(mFGSurfaceControl).apply();
+    sp<SurfaceControl> parentLayer;
+    ASSERT_NO_FATAL_FAILURE(parentLayer = createLayer("parent-test", 0, 0,
+                                                      ISurfaceComposerClient::eHidden,
+                                                      mRootSurfaceControl.get()));
+
+    sp<SurfaceControl> childLayer;
+    ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("child-test", 0, 0,
+                                                     ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                     parentLayer.get()));
+    ASSERT_NO_FATAL_FAILURE(
+            fillBufferLayerColor(childLayer, Color::GREEN, size.width(), size.height()));
+
+    // hide the parent layer to ensure secure flag is passed down to child when screenshotting
+    Transaction().setLayer(parentLayer, INT32_MAX).show(childLayer).apply(true);
+    Transaction()
+            .setFlags(parentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+            .apply();
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = childLayer->getHandle();
+    captureArgs.sourceCrop = size;
+    captureArgs.captureSecureLayers = false;
+    {
+        SCOPED_TRACE("parent hidden");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::BLACK);
+    }
+
+    captureArgs.captureSecureLayers = true;
+    {
+        SCOPED_TRACE("capture secure parent not visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::GREEN);
+    }
+
+    Transaction().show(parentLayer).apply();
+    captureArgs.captureSecureLayers = false;
+    {
+        SCOPED_TRACE("parent visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::BLACK);
+    }
+
+    captureArgs.captureSecureLayers = true;
+    {
+        SCOPED_TRACE("capture secure parent visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::GREEN);
+    }
+}
+
+TEST_F(ScreenCaptureTest, CaptureOffscreenChildRespectsParentSecureFlag) {
+    Rect size(0, 0, 100, 100);
+    Transaction().hide(mBGSurfaceControl).hide(mFGSurfaceControl).apply();
+    // Parent layer should be offscreen.
+    sp<SurfaceControl> parentLayer;
+    ASSERT_NO_FATAL_FAILURE(
+            parentLayer = createLayer("parent-test", 0, 0, ISurfaceComposerClient::eHidden));
+
+    sp<SurfaceControl> childLayer;
+    ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("child-test", 0, 0,
+                                                     ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                     parentLayer.get()));
+    ASSERT_NO_FATAL_FAILURE(
+            fillBufferLayerColor(childLayer, Color::GREEN, size.width(), size.height()));
+
+    // hide the parent layer to ensure secure flag is passed down to child when screenshotting
+    Transaction().setLayer(parentLayer, INT32_MAX).show(childLayer).apply(true);
+    Transaction()
+            .setFlags(parentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+            .apply();
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = childLayer->getHandle();
+    captureArgs.sourceCrop = size;
+    captureArgs.captureSecureLayers = false;
+    {
+        SCOPED_TRACE("parent hidden");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::BLACK);
+    }
+
+    captureArgs.captureSecureLayers = true;
+    {
+        SCOPED_TRACE("capture secure parent not visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::GREEN);
+    }
+
+    Transaction().show(parentLayer).apply();
+    captureArgs.captureSecureLayers = false;
+    {
+        SCOPED_TRACE("parent visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::BLACK);
+    }
+
+    captureArgs.captureSecureLayers = true;
+    {
+        SCOPED_TRACE("capture secure parent visible");
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(size, Color::GREEN);
+    }
+}
+
 TEST_F(ScreenCaptureTest, CaptureSingleLayer) {
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mBGSurfaceControl->getHandle();
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index 2d87ddd..3873b0c 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -30,7 +30,7 @@
 
 class InitiateModeChangeTest : public DisplayTransactionTest {
 public:
-    using Action = DisplayDevice::DesiredActiveModeAction;
+    using Action = DisplayDevice::DesiredModeAction;
     using Event = scheduler::DisplayModeEvent;
 
     void SetUp() override {
@@ -66,108 +66,93 @@
             ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
 };
 
-TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) {
+TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) {
     EXPECT_EQ(Action::None,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{60_Hz, kMode60}, Event::None}));
-    EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{60_Hz, kMode60}, Event::None}));
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 }
 
-TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setNewMode) {
+TEST_F(InitiateModeChangeTest, setDesiredMode) {
     EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredActiveMode()->modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredMode()->modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getDesiredMode()->event);
 
-    // Setting another mode should be cached but return None
+    // Setting another mode should be cached but return None.
     EXPECT_EQ(Action::None,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getDesiredActiveMode()->modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getDesiredMode()->modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getDesiredMode()->event);
 }
 
-TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) {
+TEST_F(InitiateModeChangeTest, clearDesiredMode) {
     EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
+    EXPECT_TRUE(mDisplay->getDesiredMode());
 
-    mDisplay->clearDesiredActiveModeState();
-    ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+    mDisplay->clearDesiredMode();
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 }
 
-TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(InitiateModeChangeTest, initiateModeChange) REQUIRES(kMainThreadContext) {
     EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredActiveMode()->modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredMode()->modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getDesiredMode()->event);
 
-    hal::VsyncPeriodChangeConstraints constraints{
+    const hal::VsyncPeriodChangeConstraints constraints{
             .desiredTimeNanos = systemTime(),
             .seamlessRequired = false,
     };
     hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_EQ(OK,
-              mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
-                                           &timeline));
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
+    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getPendingMode().modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getPendingMode().event);
 
-    mDisplay->clearDesiredActiveModeState();
-    ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+    mDisplay->clearDesiredMode();
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 }
 
-TEST_F(InitiateModeChangeTest, initiateRenderRateChange) {
+TEST_F(InitiateModeChangeTest, initiateRenderRateSwitch) {
     EXPECT_EQ(Action::InitiateRenderRateSwitch,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{30_Hz, kMode60}, Event::None}));
-    EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{30_Hz, kMode60}, Event::None}));
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 }
 
-TEST_F(InitiateModeChangeTest, getUpcomingActiveMode_desiredActiveModeChanged)
-NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(InitiateModeChangeTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
     EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredActiveMode()->modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getDesiredMode()->modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getDesiredMode()->event);
 
-    hal::VsyncPeriodChangeConstraints constraints{
+    const hal::VsyncPeriodChangeConstraints constraints{
             .desiredTimeNanos = systemTime(),
             .seamlessRequired = false,
     };
     hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_EQ(OK,
-              mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
-                                           &timeline));
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
+    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getPendingMode().modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getPendingMode().event);
 
     EXPECT_EQ(Action::None,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
-    ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getDesiredActiveMode()->modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+              mDisplay->setDesiredMode({scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getDesiredMode()->modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getDesiredMode()->event);
 
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getPendingMode().modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getPendingMode().event);
 
-    EXPECT_EQ(OK,
-              mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
-                                           &timeline));
-    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
+    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getPendingMode().modeOpt);
+    EXPECT_EQ(Event::None, mDisplay->getPendingMode().event);
 
-    mDisplay->clearDesiredActiveModeState();
-    ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+    mDisplay->clearDesiredMode();
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index c040f29..803710d 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "FlagManagerTest"
 
 #include <common/FlagManager.h>
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index e040028..9dd1431 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
 #include "gmock/gmock-spec-builders.h"
 #include "mock/MockTimeStats.h"
 #undef LOG_TAG
@@ -1059,6 +1061,9 @@
 }
 
 TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
+                      true);
+
     // setup 2 display frames
     // DF 1: [22,40] -> [5, 40]
     // DF  : [36, 70] (Skipped one, added by the trace)
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index f1e841b..4a22731 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -35,11 +35,11 @@
 #include <log/log.h>
 #include <chrono>
 
+#include <common/test/FlagUtils.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayIdentificationTestHelpers.h"
-#include "FlagUtils.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index befef48..c1059d7 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -25,7 +25,7 @@
 
 #include <renderengine/mock/FakeExternalTexture.h>
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "LayerHierarchyTest.h"
 #include "Scheduler/LayerHistory.h"
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 1adf14f..c24d397 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -27,7 +27,7 @@
 #include <gtest/gtest.h>
 #include <log/log.h>
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 047ef5a..07a522a 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -21,7 +21,7 @@
 
 #include <scheduler/Fps.h>
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 2cc6dc7..16143e3 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1055,4 +1055,23 @@
     EXPECT_FALSE(getSnapshot(1)->isFrontBuffered());
 }
 
+TEST_F(LayerSnapshotTest, setSecureRootSnapshot) {
+    setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = false,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .displayChanges = false,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    args.rootSnapshot.isSecure = true;
+    update(mSnapshotBuilder, args);
+
+    EXPECT_TRUE(getSnapshot(1)->isSecure);
+    // Ensure child is also marked as secure
+    EXPECT_TRUE(getSnapshot(11)->isSecure);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a00e886..a9567b2 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -26,6 +26,7 @@
 #include <log/log.h>
 #include <ui/Size.h>
 
+#include <common/test/FlagUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/FrameRateMode.h>
 #include "DisplayHardware/HWC2.h"
@@ -1543,6 +1544,7 @@
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, false);
     // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
 
@@ -1610,6 +1612,7 @@
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
     auto selector = createSelector(kVrrModes_60_120, kModeId120);
 
@@ -1624,15 +1627,15 @@
 
     // Note that `smoothSwitchOnly` should not have an effect.
     const std::initializer_list<Case> testCases = {
-            {FrameRateCategory::Default, false, 240_Hz},
+            {FrameRateCategory::Default, false, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
-            {FrameRateCategory::NoPreference, false, 240_Hz},
+            {FrameRateCategory::NoPreference, false, 120_Hz},
             {FrameRateCategory::Low, false, 30_Hz},
             {FrameRateCategory::Normal, false, 60_Hz},
             {FrameRateCategory::High, false, 120_Hz},
-            {FrameRateCategory::Default, true, 240_Hz},
+            {FrameRateCategory::Default, true, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
-            {FrameRateCategory::NoPreference, true, 240_Hz},
+            {FrameRateCategory::NoPreference, true, 120_Hz},
             {FrameRateCategory::Low, true, 30_Hz},
             {FrameRateCategory::Normal, true, 60_Hz},
             {FrameRateCategory::High, true, 120_Hz},
@@ -3486,10 +3489,11 @@
 
 // VRR tests
 TEST_P(RefreshRateSelectorTest, singleMinMaxRateForVrr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     auto selector = createSelector(kVrrMode_120, kModeId120);
     EXPECT_TRUE(selector.supportsFrameRateOverride());
 
@@ -3505,10 +3509,11 @@
 }
 
 TEST_P(RefreshRateSelectorTest, renderRateChangesWithPolicyChangeForVrr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     auto selector = createSelector(kVrrModes_60_120, kModeId120);
 
     const FpsRange only120 = {120_Hz, 120_Hz};
@@ -3562,10 +3567,11 @@
 }
 
 TEST_P(RefreshRateSelectorTest, modeChangesWithPolicyChangeForVrr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     auto selector = createSelector(kVrrModes_60_120, kModeId120);
 
     const FpsRange range120 = {0_Hz, 120_Hz};
@@ -3585,10 +3591,11 @@
 }
 
 TEST_P(RefreshRateSelectorTest, getFrameRateOverridesForVrr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     auto selector = createSelector(kVrrMode_120, kModeId120);
     // TODO(b/297600226) Run at lower than 30 Fps for dVRR
     const std::vector<Fps> desiredRefreshRates = {30_Hz, 34.285_Hz, 40_Hz, 48_Hz,
@@ -3614,10 +3621,11 @@
 }
 
 TEST_P(RefreshRateSelectorTest, renderFrameRatesForVrr) {
-    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
     }
 
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
     auto selector = createSelector(kVrrMode_120, kModeId120);
     const FpsRange only120 = {120_Hz, 120_Hz};
     const FpsRange range120 = {0_Hz, 120_Hz};
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index b5eb777..8a8f771 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -457,26 +457,51 @@
     using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
 
     struct Compositor final : ICompositor {
-        VsyncIds vsyncIds;
+        explicit Compositor(TestableScheduler& scheduler) : scheduler(scheduler) {}
+
+        TestableScheduler& scheduler;
+
+        struct {
+            PhysicalDisplayId commit;
+            PhysicalDisplayId composite;
+        } pacesetterIds;
+
+        struct {
+            VsyncIds commit;
+            VsyncIds composite;
+        } vsyncIds;
+
         bool committed = true;
+        bool changePacesetter = false;
 
         void configure() override {}
 
-        bool commit(PhysicalDisplayId, const scheduler::FrameTargets& targets) override {
-            vsyncIds.clear();
+        bool commit(PhysicalDisplayId pacesetterId,
+                    const scheduler::FrameTargets& targets) override {
+            pacesetterIds.commit = pacesetterId;
+
+            vsyncIds.commit.clear();
+            vsyncIds.composite.clear();
 
             for (const auto& [id, target] : targets) {
-                vsyncIds.emplace_back(id, target->vsyncId());
+                vsyncIds.commit.emplace_back(id, target->vsyncId());
+            }
+
+            if (changePacesetter) {
+                scheduler.setPacesetterDisplay(kDisplayId2);
             }
 
             return committed;
         }
 
-        CompositeResultsPerDisplay composite(PhysicalDisplayId,
-                                             const scheduler::FrameTargeters&) override {
+        CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+                                             const scheduler::FrameTargeters& targeters) override {
+            pacesetterIds.composite = pacesetterId;
+
             CompositeResultsPerDisplay results;
 
-            for (const auto& [id, _] : vsyncIds) {
+            for (const auto& [id, targeter] : targeters) {
+                vsyncIds.composite.emplace_back(id, targeter->target().vsyncId());
                 results.try_emplace(id,
                                     CompositeResult{.compositionCoverage =
                                                             CompositionCoverage::Hwc});
@@ -486,21 +511,41 @@
         }
 
         void sample() override {}
-    } compositor;
+    } compositor(*mScheduler);
 
     mScheduler->doFrameSignal(compositor, VsyncId(42));
 
-    const auto makeVsyncIds = [](VsyncId vsyncId) -> VsyncIds {
-        return {{kDisplayId1, vsyncId}, {kDisplayId2, vsyncId}};
+    const auto makeVsyncIds = [](VsyncId vsyncId, bool swap = false) -> VsyncIds {
+        if (swap) {
+            return {{kDisplayId2, vsyncId}, {kDisplayId1, vsyncId}};
+        } else {
+            return {{kDisplayId1, vsyncId}, {kDisplayId2, vsyncId}};
+        }
     };
 
-    EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds);
+    EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+    EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite);
+    EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.commit);
+    EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.composite);
 
+    // FrameTargets should be updated despite the skipped commit.
     compositor.committed = false;
     mScheduler->doFrameSignal(compositor, VsyncId(43));
 
-    // FrameTargets should be updated despite the skipped commit.
-    EXPECT_EQ(makeVsyncIds(VsyncId(43)), compositor.vsyncIds);
+    EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+    EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite);
+    EXPECT_EQ(makeVsyncIds(VsyncId(43)), compositor.vsyncIds.commit);
+    EXPECT_TRUE(compositor.vsyncIds.composite.empty());
+
+    // The pacesetter may change during commit.
+    compositor.committed = true;
+    compositor.changePacesetter = true;
+    mScheduler->doFrameSignal(compositor, VsyncId(44));
+
+    EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+    EXPECT_EQ(kDisplayId2, compositor.pacesetterIds.composite);
+    EXPECT_EQ(makeVsyncIds(VsyncId(44)), compositor.vsyncIds.commit);
+    EXPECT_EQ(makeVsyncIds(VsyncId(44), true), compositor.vsyncIds.composite);
 }
 
 class AttachedChoreographerTest : public SchedulerTest {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index aeac80d..3558ba6 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -56,6 +56,9 @@
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(
                         TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+        EXPECT_CALL(*vsyncTracker, minFramePeriod())
+                .WillRepeatedly(Return(Period::fromNs(
+                        TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
                            .setRefreshRateSelector(std::move(selectorPtr))
@@ -138,6 +141,9 @@
     EXPECT_CALL(*vsyncTracker, currentPeriod())
             .WillRepeatedly(
                     Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, minFramePeriod())
+            .WillRepeatedly(Return(Period::fromNs(
+                    TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
     EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
     mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
                             std::move(eventThread), std::move(sfEventThread),
@@ -145,11 +151,11 @@
                             TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) {
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
@@ -157,9 +163,9 @@
                                         mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
                                                                      120));
 
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getDesiredMode()->modeOpt->modePtr->getId(), kModeId90);
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
@@ -171,8 +177,9 @@
     mFlinger.commit();
 
     Mock::VerifyAndClearExpectations(mComposer);
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+
+    EXPECT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that the next commit will complete the mode change and send
     // a onModeChanged event to the framework.
@@ -182,14 +189,14 @@
     mFlinger.commit();
     Mock::VerifyAndClearExpectations(mAppEventThread);
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) {
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    EXPECT_FALSE(mDisplay->getDesiredMode());
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
@@ -197,9 +204,9 @@
                                         mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
                                                                      120));
 
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getDesiredMode()->modeOpt->modePtr->getId(), kModeId90);
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
@@ -214,8 +221,8 @@
 
     mFlinger.commit();
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
 }
 
 TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
@@ -224,8 +231,8 @@
     // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
     // is still being processed the later call will be respected.
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
@@ -245,8 +252,8 @@
                                         mock::createDisplayModeSpecs(kModeId120.value(), false, 0,
                                                                      180));
 
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120);
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getDesiredMode()->modeOpt->modePtr->getId(), kModeId120);
 
     EXPECT_CALL(*mComposer,
                 setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
@@ -255,20 +262,20 @@
 
     mFlinger.commit();
 
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120);
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getDesiredMode()->modeOpt->modePtr->getId(), kModeId120);
 
     mFlinger.commit();
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) {
+TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
 
@@ -276,9 +283,9 @@
                                         mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
                                                                      120));
 
-    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90_4K);
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getDesiredMode()->modeOpt->modePtr->getId(), kModeId90_4K);
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
@@ -312,18 +319,18 @@
     // so we need to update with the new instance.
     mDisplay = mFlinger.getDisplay(displayToken);
 
-    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
+    EXPECT_FALSE(mDisplay->getDesiredMode());
+    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
 }
 
 MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
-    if (!arg->getDesiredActiveMode()) {
-        *result_listener << "No desired active mode";
+    if (!arg->getDesiredMode()) {
+        *result_listener << "No desired mode";
         return false;
     }
 
-    if (arg->getDesiredActiveMode()->modeOpt->modePtr->getId() != modeId) {
-        *result_listener << "Unexpected desired active mode " << modeId;
+    if (arg->getDesiredMode()->modeOpt->modePtr->getId() != modeId) {
+        *result_listener << "Unexpected desired mode " << modeId;
         return false;
     }
 
@@ -336,9 +343,8 @@
 }
 
 MATCHER_P(ModeSettledTo, modeId, "") {
-    if (const auto desiredOpt = arg->getDesiredActiveMode()) {
-        *result_listener << "Unsettled desired active mode "
-                         << desiredOpt->modeOpt->modePtr->getId();
+    if (const auto desiredOpt = arg->getDesiredMode()) {
+        *result_listener << "Unsettled desired mode " << desiredOpt->modeOpt->modePtr->getId();
         return false;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index cf3fab3..31e1330 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -25,6 +25,11 @@
 namespace android {
 namespace {
 
+MATCHER_P(DisplayModeFps, value, "equals") {
+    using fps_approx_ops::operator==;
+    return arg->getVsyncRate() == value;
+}
+
 // Used when we simulate a display that supports doze.
 template <typename Display>
 struct DozeIsSupportedVariant {
@@ -94,7 +99,8 @@
     static void setupResetModelCallExpectations(DisplayTransactionTest* test) {
         auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule();
         EXPECT_CALL(static_cast<mock::VsyncController&>(vsyncSchedule->getController()),
-                    startPeriodTransition(DEFAULT_VSYNC_PERIOD, false))
+                    onDisplayModeChanged(DisplayModeFps(Fps::fromPeriodNsecs(DEFAULT_VSYNC_PERIOD)),
+                                         false))
                 .Times(1);
         EXPECT_CALL(static_cast<mock::VSyncTracker&>(vsyncSchedule->getTracker()), resetModel())
                 .Times(1);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bca14f5..cf48c76 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -306,6 +306,9 @@
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+        EXPECT_CALL(*vsyncTracker, minFramePeriod())
+                .WillRepeatedly(
+                        Return(Period::fromNs(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
         setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
                        std::move(sfEventThread), DefaultDisplayMode{options.displayId},
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 4be07a1..6a56353 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -34,12 +34,36 @@
     return std::chrono::duration_cast<std::chrono::nanoseconds>(tp).count();
 }
 
-class FixedRateIdealStubTracker : public VSyncTracker {
+class StubTracker : public VSyncTracker {
 public:
-    FixedRateIdealStubTracker() : mPeriod{toNs(3ms)} {}
+    StubTracker(nsecs_t period) : mPeriod(period) {}
 
     bool addVsyncTimestamp(nsecs_t) final { return true; }
 
+    nsecs_t currentPeriod() const final {
+        std::lock_guard lock(mMutex);
+        return mPeriod;
+    }
+
+    Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); }
+    void resetModel() final {}
+    bool needsMoreSamples() const final { return false; }
+    bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
+    void setRenderRate(Fps) final {}
+    void onFrameBegin(TimePoint, TimePoint) final {}
+    void onFrameMissed(TimePoint) final {}
+    void dump(std::string&) const final {}
+
+protected:
+    std::mutex mutable mMutex;
+    nsecs_t mPeriod;
+};
+
+class FixedRateIdealStubTracker : public StubTracker {
+public:
+    FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
+
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
         auto const floor = timePoint % mPeriod;
         if (floor == 0) {
@@ -47,25 +71,11 @@
         }
         return timePoint - floor + mPeriod;
     }
-
-    nsecs_t currentPeriod() const final { return mPeriod; }
-
-    void setPeriod(nsecs_t) final {}
-    void resetModel() final {}
-    bool needsMoreSamples() const final { return false; }
-    bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
-    void setDisplayModeData(const DisplayModeData&) final {}
-    void dump(std::string&) const final {}
-
-private:
-    nsecs_t const mPeriod;
 };
 
-class VRRStubTracker : public VSyncTracker {
+class VRRStubTracker : public StubTracker {
 public:
-    VRRStubTracker(nsecs_t period) : mPeriod{period} {}
-
-    bool addVsyncTimestamp(nsecs_t) final { return true; }
+    VRRStubTracker(nsecs_t period) : StubTracker(period) {}
 
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
         std::lock_guard lock(mMutex);
@@ -83,21 +93,7 @@
         mBase = last_known;
     }
 
-    nsecs_t currentPeriod() const final {
-        std::lock_guard lock(mMutex);
-        return mPeriod;
-    }
-
-    void setPeriod(nsecs_t) final {}
-    void resetModel() final {}
-    bool needsMoreSamples() const final { return false; }
-    bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
-    void setDisplayModeData(const DisplayModeData&) final {}
-    void dump(std::string&) const final {}
-
 private:
-    std::mutex mutable mMutex;
-    nsecs_t mPeriod;
     nsecs_t mBase = 0;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 8310866..2047018 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -30,9 +30,10 @@
 
 #include <scheduler/TimeKeeper.h>
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncDispatchTimerQueue.h"
 #include "Scheduler/VSyncTracker.h"
+#include "mock/MockVSyncTracker.h"
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
@@ -42,7 +43,7 @@
 namespace android::scheduler {
 using namespace com::android::graphics::surfaceflinger;
 
-class MockVSyncTracker : public VSyncTracker {
+class MockVSyncTracker : public mock::VSyncTracker {
 public:
     MockVSyncTracker(nsecs_t period) : mPeriod{period} {
         ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_))
@@ -52,16 +53,6 @@
                 .WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod));
     }
 
-    MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
-    MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
-    MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
-    MOCK_METHOD1(setPeriod, void(nsecs_t));
-    MOCK_METHOD0(resetModel, void());
-    MOCK_CONST_METHOD0(needsMoreSamples, bool());
-    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setDisplayModeData, (const DisplayModeData&), (override));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
-
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
             return timePoint;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 30a2855..7a498c9 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -23,8 +23,9 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 #define LOG_NDEBUG 0
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncPredictor.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
@@ -39,12 +40,25 @@
 using namespace std::literals;
 using namespace com::android::graphics::surfaceflinger;
 
+using NotifyExpectedPresentConfig =
+        ::aidl::android::hardware::graphics::composer3::VrrConfig::NotifyExpectedPresentConfig;
+
+using android::mock::createDisplayMode;
+using android::mock::createDisplayModeBuilder;
+using android::mock::createVrrDisplayMode;
+
 namespace android::scheduler {
 
+namespace {
 MATCHER_P2(IsCloseTo, value, tolerance, "is within tolerance") {
     return arg <= value + tolerance && arg >= value - tolerance;
 }
 
+MATCHER_P(FpsMatcher, value, "equals") {
+    using fps_approx_ops::operator==;
+    return arg == value;
+}
+
 std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs_t bias) {
     std::vector<nsecs_t> vsyncs(count);
     std::generate(vsyncs.begin(), vsyncs.end(),
@@ -54,21 +68,27 @@
 
 constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
 
+ftl::NonNull<DisplayModePtr> displayMode(nsecs_t period) {
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(period);
+    return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution,
+                                              DEFAULT_DISPLAY_ID));
+}
+} // namespace
+
 struct VSyncPredictorTest : testing::Test {
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
+    ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod);
     scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
     static constexpr size_t kHistorySize = 10;
     static constexpr size_t kMinimumSamplesForPrediction = 6;
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
-    VSyncPredictor tracker{DEFAULT_DISPLAY_ID,
-                           mPeriod,
-                           kHistorySize,
-                           kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent,
-                           mVsyncTrackerCallback};
+    VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent, mVsyncTrackerCallback};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -78,7 +98,7 @@
     EXPECT_THAT(model.intercept, Eq(0));
 
     auto const changedPeriod = 2000;
-    tracker.setPeriod(changedPeriod);
+    tracker.setDisplayModePtr(displayMode(changedPeriod));
     model = tracker.getVSyncPredictionModel();
     EXPECT_THAT(model.slope, Eq(changedPeriod));
     EXPECT_THAT(model.intercept, Eq(0));
@@ -99,7 +119,7 @@
     EXPECT_FALSE(tracker.needsMoreSamples());
 
     auto const changedPeriod = mPeriod * 2;
-    tracker.setPeriod(changedPeriod);
+    tracker.setDisplayModePtr(displayMode(changedPeriod));
     EXPECT_TRUE(tracker.needsMoreSamples());
 
     for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
@@ -133,7 +153,7 @@
     }
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + slightlyLessPeriod));
-    tracker.setPeriod(changedPeriod);
+    tracker.setDisplayModePtr(displayMode(changedPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + changedPeriod));
 }
 
@@ -179,7 +199,7 @@
     auto constexpr expectedPeriod = 16639242;
     auto constexpr expectedIntercept = 1049341;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -198,7 +218,7 @@
     auto expectedPeriod = 11089413;
     auto expectedIntercept = 94421;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -225,7 +245,7 @@
     auto expectedPeriod = 45450152;
     auto expectedIntercept = 469647;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -251,7 +271,7 @@
     auto expectedPeriod = 1999892;
     auto expectedIntercept = 86342;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -271,7 +291,7 @@
     auto const simulatedVsyncsSlow =
             generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);
 
-    tracker.setPeriod(fastPeriod);
+    tracker.setDisplayModePtr(displayMode(fastPeriod));
     for (auto const& timestamp : simulatedVsyncsFast) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -281,7 +301,7 @@
     EXPECT_THAT(model.slope, IsCloseTo(fastPeriod, mMaxRoundingError));
     EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
 
-    tracker.setPeriod(slowPeriod);
+    tracker.setDisplayModePtr(displayMode(slowPeriod));
     for (auto const& timestamp : simulatedVsyncsSlow) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -305,7 +325,7 @@
             generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod2, fastTimeBase);
 
     auto idealPeriod = 100000;
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncsFast) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -313,14 +333,14 @@
     EXPECT_THAT(model.slope, Eq(fastPeriod));
     EXPECT_THAT(model.intercept, Eq(0));
 
-    tracker.setPeriod(slowPeriod);
+    tracker.setDisplayModePtr(displayMode(slowPeriod));
     for (auto const& timestamp : simulatedVsyncsSlow) {
         tracker.addVsyncTimestamp(timestamp);
     }
 
     // we had a model for 100ns mPeriod before, use that until the new samples are
     // sufficiently built up
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     model = tracker.getVSyncPredictionModel();
     EXPECT_THAT(model.slope, Eq(fastPeriod));
     EXPECT_THAT(model.intercept, Eq(0));
@@ -369,7 +389,7 @@
     auto const expectedPeriod = 11113919;
     auto const expectedIntercept = -1195945;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -388,11 +408,8 @@
 
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
-    VSyncPredictor tracker{DEFAULT_DISPLAY_ID,
-                           mPeriod,
-                           20,
-                           kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent,
+    const auto mode = displayMode(mPeriod);
+    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent,
                            mVsyncTrackerCallback};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
@@ -407,7 +424,7 @@
     auto const expectedPeriod = 16698426;
     auto const expectedIntercept = 58055;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -420,7 +437,7 @@
 TEST_F(VSyncPredictorTest, resetsWhenInstructed) {
     auto const idealPeriod = 10000;
     auto const realPeriod = 10500;
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto i = 0; i < kMinimumSamplesForPrediction; i++) {
         tracker.addVsyncTimestamp(i * realPeriod);
     }
@@ -562,7 +579,7 @@
     auto constexpr expectedPeriod = 16'644'742;
     auto constexpr expectedIntercept = 125'626;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -580,7 +597,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setDisplayModeData({.renderRate = Fps::fromPeriodNsecs(3 * mPeriod)});
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -602,12 +619,12 @@
 
     const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
 
-    tracker.setDisplayModeData({.renderRate = refreshRate / 4});
+    tracker.setRenderRate(refreshRate / 4);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
 
-    tracker.setDisplayModeData({.renderRate = refreshRate / 2});
+    tracker.setRenderRate(refreshRate / 2);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
@@ -615,7 +632,7 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
 
-    tracker.setDisplayModeData({.renderRate = refreshRate / 6});
+    tracker.setRenderRate(refreshRate / 6);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
 }
@@ -629,7 +646,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setDisplayModeData({.renderRate = Fps::fromPeriodNsecs(3.5f * mPeriod)});
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -642,16 +659,27 @@
 
 TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
     const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-    DisplayModeData displayModeData =
-            DisplayModeData{.renderRate = refreshRate,
-                            .notifyExpectedPresentTimeoutOpt = Period::fromNs(30)};
-    tracker.setDisplayModeData(displayModeData);
+    NotifyExpectedPresentConfig notifyExpectedPresentConfig;
+    notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs = Period::fromNs(30).ns();
+
+    hal::VrrConfig vrrConfig;
+    vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
+    vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs();
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto mode =
+            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup,
+                                                  kResolution, DEFAULT_DISPLAY_ID));
+
+    tracker.setDisplayModePtr(mode);
     auto last = mNow;
     for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
         EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(DEFAULT_DISPLAY_ID, TimePoint::fromNs(last + mPeriod),
-                                     displayModeData, Period::fromNs(mPeriod)))
+                    onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode,
+                                     FpsMatcher(refreshRate)))
                 .Times(1);
         EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
         mNow += mPeriod;
@@ -659,20 +687,50 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    displayModeData = DisplayModeData{.renderRate = refreshRate / 2,
-                                      .notifyExpectedPresentTimeoutOpt = Period::fromNs(30)};
-    tracker.setDisplayModeData(displayModeData);
+    tracker.setRenderRate(refreshRate / 2);
     {
         // out of render rate phase
         EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(DEFAULT_DISPLAY_ID, TimePoint::fromNs(mNow + 3 * mPeriod),
-                                     displayModeData, Period::fromNs(mPeriod)))
+                    onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode,
+                                     FpsMatcher(refreshRate / 2)))
                 .Times(1);
         EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
                     Eq(mNow + 3 * mPeriod));
     }
 }
 
+TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
+                              kOutlierTolerancePercent, mVsyncTrackerCallback};
+
+    vrrTracker.setRenderRate(minFrameRate);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+
+    vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+    EXPECT_EQ(1500, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+
+    vrrTracker.onFrameMissed(TimePoint::fromNs(2500));
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3300));
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index aca3ccc..8d9623d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -31,6 +31,9 @@
 
 #include <scheduler/TimeKeeper.h>
 
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVSyncTracker.h"
+
 #include "Scheduler/VSyncDispatch.h"
 #include "Scheduler/VSyncReactor.h"
 #include "Scheduler/VSyncTracker.h"
@@ -40,20 +43,7 @@
 
 namespace android::scheduler {
 
-class MockVSyncTracker : public VSyncTracker {
-public:
-    MockVSyncTracker() { ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true)); }
-    MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
-    MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
-    MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
-    MOCK_METHOD1(setPeriod, void(nsecs_t));
-    MOCK_METHOD0(resetModel, void());
-    MOCK_CONST_METHOD0(needsMoreSamples, bool());
-    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setDisplayModeData, (const DisplayModeData&), (override));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
-};
-
+namespace {
 class MockClock : public Clock {
 public:
     MOCK_CONST_METHOD0(now, nsecs_t());
@@ -93,18 +83,33 @@
 
 constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
 
+ftl::NonNull<DisplayModePtr> displayMode(nsecs_t vsyncPeriod) {
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(vsyncPeriod);
+    return ftl::as_non_null(mock::createDisplayMode(DisplayModeId(0), refreshRate, kGroup,
+                                                    kResolution, DEFAULT_DISPLAY_ID));
+}
+
+MATCHER_P(DisplayModeMatcher, value, "display mode equals") {
+    return arg->getId() == value->getId() && equalsExceptDisplayModeId(*arg, *value);
+}
+
+} // namespace
+
 class VSyncReactorTest : public testing::Test {
 protected:
     VSyncReactorTest()
-          : mMockTracker(std::make_shared<NiceMock<MockVSyncTracker>>()),
+          : mMockTracker(std::make_shared<NiceMock<mock::VSyncTracker>>()),
             mMockClock(std::make_shared<NiceMock<MockClock>>()),
             mReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock), *mMockTracker,
                      kPendingLimit, false /* supportKernelIdleTimer */) {
         ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow));
         ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period));
+        ON_CALL(*mMockTracker, addVsyncTimestamp(_)).WillByDefault(Return(true));
     }
 
-    std::shared_ptr<MockVSyncTracker> mMockTracker;
+    std::shared_ptr<mock::VSyncTracker> mMockTracker;
     std::shared_ptr<MockClock> mMockClock;
     static constexpr size_t kPendingLimit = 3;
     static constexpr nsecs_t mDummyTime = 47;
@@ -194,7 +199,8 @@
     mReactor.setIgnorePresentFences(true);
 
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod, false);
+
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -206,8 +212,8 @@
 
 TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
     nsecs_t const newPeriod = 5000;
-    EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
-    mReactor.startPeriodTransition(newPeriod, false);
+    EXPECT_CALL(*mMockTracker, setDisplayModePtr(_)).Times(0);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed));
@@ -217,7 +223,7 @@
     EXPECT_FALSE(periodFlushed);
 
     Mock::VerifyAndClearExpectations(mMockTracker.get());
-    EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+    EXPECT_CALL(*mMockTracker, setDisplayModePtr(/*displayMode(newPeriod)*/ _)).Times(1);
 
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(25000, std::nullopt, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
@@ -226,7 +232,7 @@
 TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
     nsecs_t sampleTime = 0;
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -234,7 +240,7 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 
-    mReactor.startPeriodTransition(period, false);
+    mReactor.onDisplayModeChanged(displayMode(period), false);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 }
@@ -244,13 +250,13 @@
     nsecs_t const secondPeriod = 5000;
     nsecs_t const thirdPeriod = 2000;
 
-    mReactor.startPeriodTransition(secondPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(secondPeriod), false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
-    mReactor.startPeriodTransition(thirdPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(thirdPeriod), false);
     EXPECT_TRUE(
             mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -291,21 +297,22 @@
 
 TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) {
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
     EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) {
     nsecs_t const newPeriod = 5000;
-    EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
-    mReactor.startPeriodTransition(newPeriod, false);
+    EXPECT_CALL(*mMockTracker, setDisplayModePtr(_)).Times(0);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
     Mock::VerifyAndClearExpectations(mMockTracker.get());
 
-    EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+    EXPECT_CALL(*mMockTracker, setDisplayModePtr(DisplayModeMatcher(displayMode(newPeriod))))
+            .Times(1);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 }
@@ -323,7 +330,7 @@
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
 
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     auto time = 0;
     auto constexpr numTimestampSubmissions = 10;
@@ -348,7 +355,7 @@
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
 
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     auto time = 0;
     // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps.
@@ -365,7 +372,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     time += period;
     mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed);
@@ -381,7 +388,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     static auto constexpr numSamplesWithNewPeriod = 4;
     Sequence seq;
@@ -408,7 +415,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     Sequence seq;
     EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -428,7 +435,7 @@
     nsecs_t const newPeriod1 = 4000;
     nsecs_t const newPeriod2 = 7000;
 
-    mReactor.startPeriodTransition(newPeriod1, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod1), false);
 
     Sequence seq;
     EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -447,7 +454,7 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
 
-    mReactor.startPeriodTransition(newPeriod2, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod2), false);
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
@@ -460,7 +467,7 @@
     mReactor.setIgnorePresentFences(true);
 
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod, false);
+    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
 
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -484,7 +491,7 @@
 
     // First, set the same period, which should only be confirmed when we receive two
     // matching callbacks
-    idleReactor.startPeriodTransition(10000, false);
+    idleReactor.onDisplayModeChanged(displayMode(10000), false);
     EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
     // Correct period but incorrect timestamp delta
@@ -497,7 +504,7 @@
     // Then, set a new period, which should be confirmed as soon as we receive a callback
     // reporting the new period
     nsecs_t const newPeriod = 5000;
-    idleReactor.startPeriodTransition(newPeriod, false);
+    idleReactor.onDisplayModeChanged(displayMode(newPeriod), false);
     // Incorrect timestamp delta and period
     EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
index a8a3cd0..bfdd596 100644
--- a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
@@ -25,10 +25,12 @@
 #include <scheduler/Fps.h>
 #include "Scheduler/VsyncSchedule.h"
 #include "ThreadContext.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
+using android::mock::createDisplayMode;
 using testing::_;
 
 namespace android {
@@ -157,35 +159,35 @@
     // allowed.
     ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
 
-    const Period period = (60_Hz).getPeriod();
+    const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
 
     EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
-    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false));
+    EXPECT_CALL(getController(), onDisplayModeChanged(mode, false));
 
-    mVsyncSchedule->startPeriodTransition(period, false);
+    mVsyncSchedule->onDisplayModeChanged(mode, false);
 }
 
 TEST_F(VsyncScheduleTest, StartPeriodTransitionAlreadyEnabled) {
     ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
     mVsyncSchedule->enableHardwareVsync();
 
-    const Period period = (60_Hz).getPeriod();
+    const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
 
     EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
-    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false));
+    EXPECT_CALL(getController(), onDisplayModeChanged(mode, false));
 
-    mVsyncSchedule->startPeriodTransition(period, false);
+    mVsyncSchedule->onDisplayModeChanged(mode, false);
 }
 
 TEST_F(VsyncScheduleTest, StartPeriodTransitionForce) {
     ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
 
-    const Period period = (60_Hz).getPeriod();
+    const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
 
     EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
-    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), true));
+    EXPECT_CALL(getController(), onDisplayModeChanged(mode, true));
 
-    mVsyncSchedule->startPeriodTransition(period, true);
+    mVsyncSchedule->onDisplayModeChanged(mode, true);
 }
 
 TEST_F(VsyncScheduleTest, AddResyncSampleDisallowed) {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index cb05c00..5bcce50 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -52,6 +52,7 @@
             .setVrrConfig(std::move(vrrConfig))
             .build();
 }
+
 inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
     return DisplayMode::Builder(modePtr->getHwcId())
             .setId(modePtr->getId())
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
index bcccae5..cc24f42 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
@@ -17,9 +17,13 @@
 #include "mock/MockVSyncTracker.h"
 
 namespace android::mock {
+using testing::Return;
 
 // Explicit default instantiation is recommended.
-VSyncTracker::VSyncTracker() = default;
 VSyncTracker::~VSyncTracker() = default;
 
+VSyncTracker::VSyncTracker() {
+    ON_CALL(*this, minFramePeriod()).WillByDefault(Return(Period::fromNs(0)));
+}
+
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 31eb86e..e588bb9 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -27,15 +27,18 @@
     VSyncTracker();
     ~VSyncTracker() override;
 
-    MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
-    MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
-    MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
-    MOCK_METHOD1(setPeriod, void(nsecs_t));
-    MOCK_METHOD0(resetModel, void());
-    MOCK_CONST_METHOD0(needsMoreSamples, bool());
-    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
-    MOCK_METHOD(void, setDisplayModeData, (const scheduler::DisplayModeData&), (override));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
+    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t), (const, override));
+    MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
+    MOCK_METHOD(Period, minFramePeriod, (), (const, override));
+    MOCK_METHOD(void, resetModel, (), (override));
+    MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
+    MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override));
+    MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
+    MOCK_METHOD(void, setRenderRate, (Fps), (override));
+    MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
+    MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
index 69ec60a..f743390 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
@@ -29,7 +29,7 @@
 
     MOCK_METHOD(bool, addPresentFence, (std::shared_ptr<FenceTime>), (override));
     MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional<nsecs_t>, bool*), (override));
-    MOCK_METHOD(void, startPeriodTransition, (nsecs_t, bool), (override));
+    MOCK_METHOD(void, onDisplayModeChanged, (ftl::NonNull<DisplayModePtr>, bool), (override));
     MOCK_METHOD(void, setIgnorePresentFences, (bool), (override));
     MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override));
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
index b8e24e0..b48529f 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
@@ -23,13 +23,10 @@
 namespace android::scheduler::mock {
 
 struct VsyncTrackerCallback final : IVsyncTrackerCallback {
-    MOCK_METHOD(void, onVsyncGenerated,
-                (PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&, Period),
-                (override));
+    MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
 };
 
 struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
-    void onVsyncGenerated(PhysicalDisplayId, TimePoint, const scheduler::DisplayModeData&,
-                          Period) override{};
+    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{};
 };
 } // namespace android::scheduler::mock
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 5403baf..2002bdf 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -59,12 +59,6 @@
         "-Wunreachable-code",
     ],
 
-    // FIXME: Workaround LTO build breakage
-    // http://b/241699694
-    lto: {
-        never: true,
-    },
-
     local_include_dirs: ["include"],
 
     export_include_dirs: ["include"],