Merge "Add bounce keys input filter" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 17e5cc1..83b336c 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1035,8 +1035,6 @@
         CommandOptions::WithTimeoutInMs(timeout_ms).Build(), true /* verbose_duration */);
     DoRadioLogcat();
 
-    RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"});
-
     /* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */
     RunCommand("LAST LOGCAT", {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable",
                                "-v", "uid", "-d", "*:v"});
@@ -1243,7 +1241,7 @@
 
 static void DumpIpAddrAndRules() {
     /* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
-    RunCommand("NETWORK INTERFACES", {"ip", "link"});
+    RunCommand("NETWORK INTERFACES", {"ip", "-s", "link"});
     RunCommand("IPv4 ADDRESSES", {"ip", "-4", "addr", "show"});
     RunCommand("IPv6 ADDRESSES", {"ip", "-6", "addr", "show"});
     RunCommand("IP RULES", {"ip", "rule", "show"});
@@ -1526,7 +1524,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/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
index 6f5deb9..c30c00f 100644
--- a/cmds/evemu-record/main.rs
+++ b/cmds/evemu-record/main.rs
@@ -26,7 +26,7 @@
 use std::io::{BufRead, Write};
 use std::path::PathBuf;
 
-use clap::Parser;
+use clap::{Parser, ValueEnum};
 use nix::sys::time::TimeVal;
 
 mod evdev;
@@ -39,6 +39,19 @@
     device: Option<PathBuf>,
     /// The file to save the recording to. Defaults to standard output.
     output_file: Option<PathBuf>,
+
+    /// The base time that timestamps should be relative to (Android-specific extension)
+    #[arg(long, value_enum, default_value_t = TimestampBase::FirstEvent)]
+    timestamp_base: TimestampBase,
+}
+
+#[derive(Clone, Debug, ValueEnum)]
+enum TimestampBase {
+    /// The first event received from the device.
+    FirstEvent,
+
+    /// The time when the system booted.
+    Boot,
 }
 
 fn get_choice(max: u32) -> u32 {
@@ -149,7 +162,11 @@
     Ok(())
 }
 
-fn print_events(device: &evdev::Device, output: &mut impl Write) -> Result<(), Box<dyn Error>> {
+fn print_events(
+    device: &evdev::Device,
+    output: &mut impl Write,
+    timestamp_base: TimestampBase,
+) -> Result<(), Box<dyn Error>> {
     fn print_event(output: &mut impl Write, event: &evdev::InputEvent) -> Result<(), io::Error> {
         // TODO(b/302297266): Translate events into human-readable names and add those as comments.
         writeln!(
@@ -164,12 +181,15 @@
         Ok(())
     }
     let event = device.read_event()?;
-    // Due to a bug in the C implementation of evemu-play [0] that has since become part of the API,
-    // the timestamp of the first event in a recording shouldn't be exactly 0.0 seconds, so offset
-    // it by 1µs.
-    //
-    // [0]: https://gitlab.freedesktop.org/libevdev/evemu/-/commit/eba96a4d2be7260b5843e65c4b99c8b06a1f4c9d
-    let start_time = event.time - TimeVal::new(0, 1);
+    let start_time = match timestamp_base {
+        // Due to a bug in the C implementation of evemu-play [0] that has since become part of the
+        // API, the timestamp of the first event in a recording shouldn't be exactly 0.0 seconds,
+        // so offset it by 1µs.
+        //
+        // [0]: https://gitlab.freedesktop.org/libevdev/evemu/-/commit/eba96a4d2be7260b5843e65c4b99c8b06a1f4c9d
+        TimestampBase::FirstEvent => event.time - TimeVal::new(0, 1),
+        TimestampBase::Boot => TimeVal::new(0, 0),
+    };
     print_event(output, &event.offset_time_by(start_time))?;
     loop {
         let event = device.read_event()?;
@@ -188,6 +208,6 @@
         None => Box::new(io::stdout().lock()),
     };
     print_device_description(&device, &mut output)?;
-    print_events(&device, &mut output)?;
+    print_events(&device, &mut output, args.timestamp_base)?;
     Ok(())
 }
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index e14af77..c8ab8c0 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -40,6 +40,7 @@
 #include <fstream>
 #include <functional>
 #include <regex>
+#include <thread>
 #include <unordered_set>
 
 #include <android-base/file.h>
@@ -555,19 +556,33 @@
     // If the initial top-level restorecon above changed the label, then go
     // back and restorecon everything recursively
     if (inProgress || before != after) {
-        ScopedTrace tracer("label-change");
         if (existing) {
             LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
-                    << path << "; running recursive restorecon";
+                       << path << "; running recursive restorecon";
         }
 
-        // Temporary mark the folder as "in-progress" to resume in case of reboot/other failure.
-        RestoreconInProgress fence(path);
+        auto restorecon = [path, seInfo, uid]() {
+            ScopedTrace tracer("label-change");
 
-        if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
-                SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
-            PLOG(ERROR) << "Failed recursive restorecon for " << path;
-            return -1;
+            // Temporary mark the folder as "in-progress" to resume in case of reboot/other failure.
+            RestoreconInProgress fence(path);
+
+            if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
+                                                  SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
+                PLOG(ERROR) << "Failed recursive restorecon for " << path;
+                return -1;
+            }
+            return 0;
+        };
+        if (inProgress) {
+            // The previous restorecon was interrupted. It's either crashed (unlikely), or the phone
+            // was rebooted. Possibly because it took too much time. This time let's move it to a
+            // separate thread - so it won't block the rest of the OS.
+            std::thread(restorecon).detach();
+        } else {
+            if (int result = restorecon(); result) {
+                return result;
+            }
         }
     }
 
diff --git a/cmds/ip-up-vpn/Android.bp b/cmds/ip-up-vpn/Android.bp
deleted file mode 100644
index c746f7f..0000000
--- a/cmds/ip-up-vpn/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
-    name: "ip-up-vpn",
-
-    srcs: ["ip-up-vpn.c"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    shared_libs: [
-        "libcutils",
-        "liblog",
-    ],
-}
diff --git a/cmds/ip-up-vpn/ip-up-vpn.c b/cmds/ip-up-vpn/ip-up-vpn.c
deleted file mode 100644
index 71f0837..0000000
--- a/cmds/ip-up-vpn/ip-up-vpn.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2011 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 "ip-up-vpn"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/if.h>
-#include <linux/route.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <log/log.h>
-
-#define DIR "/data/misc/vpn/"
-
-static const char *env(const char *name) {
-    const char *value = getenv(name);
-    return value ? value : "";
-}
-
-static int set_address(struct sockaddr *sa, const char *address) {
-    sa->sa_family = AF_INET;
-    errno = EINVAL;
-    return inet_pton(AF_INET, address, &((struct sockaddr_in *)sa)->sin_addr);
-}
-
-/*
- * The primary goal is to create a file with VPN parameters. Currently they
- * are interface, addresses, routes, DNS servers, and search domains and VPN
- * server address. Each parameter occupies one line in the file, and it can be
- * an empty string or space-separated values. The order and the format must be
- * consistent with com.android.server.connectivity.Vpn. Here is an example.
- *
- *   ppp0
- *   192.168.1.100/24
- *   0.0.0.0/0
- *   192.168.1.1 192.168.1.2
- *   example.org
- *   192.0.2.1
- *
- * The secondary goal is to unify the outcome of VPN. The current baseline
- * is to have an interface configured with the given address and netmask
- * and maybe add a host route to protect the tunnel. PPP-based VPN already
- * does this, but others might not. Routes, DNS servers, and search domains
- * are handled by the framework since they can be overridden by the users.
- */
-int main(int argc, char **argv)
-{
-    FILE *state = fopen(DIR ".tmp", "wb");
-    if (!state) {
-        ALOGE("Cannot create state: %s", strerror(errno));
-        return 1;
-    }
-
-    if (argc >= 6) {
-        /* Invoked by pppd. */
-        fprintf(state, "%s\n", argv[1]);
-        fprintf(state, "%s/32\n", argv[4]);
-        fprintf(state, "0.0.0.0/0\n");
-        fprintf(state, "%s %s\n", env("DNS1"), env("DNS2"));
-        fprintf(state, "\n");
-        fprintf(state, "\n");
-    } else if (argc == 2) {
-        /* Invoked by racoon. */
-        const char *interface = env("INTERFACE");
-        const char *address = env("INTERNAL_ADDR4");
-        const char *routes = env("SPLIT_INCLUDE_CIDR");
-
-        int s = socket(AF_INET, SOCK_DGRAM, 0);
-        struct ifreq ifr;
-        memset(&ifr, 0, sizeof(ifr));
-
-        /* Bring up the interface. */
-        ifr.ifr_flags = IFF_UP;
-        strncpy(ifr.ifr_name, interface, IFNAMSIZ);
-        if (ioctl(s, SIOCSIFFLAGS, &ifr)) {
-            ALOGE("Cannot bring up %s: %s", interface, strerror(errno));
-            fclose(state);
-            return 1;
-        }
-
-        /* Set the address. */
-        if (!set_address(&ifr.ifr_addr, address) ||
-                ioctl(s, SIOCSIFADDR, &ifr)) {
-            ALOGE("Cannot set address: %s", strerror(errno));
-            fclose(state);
-            return 1;
-        }
-
-        /* Set the netmask. */
-        if (set_address(&ifr.ifr_netmask, env("INTERNAL_NETMASK4"))) {
-            if (ioctl(s, SIOCSIFNETMASK, &ifr)) {
-                ALOGE("Cannot set netmask: %s", strerror(errno));
-                fclose(state);
-                return 1;
-            }
-        }
-
-        /* TODO: Send few packets to trigger phase 2? */
-
-        fprintf(state, "%s\n", interface);
-        fprintf(state, "%s/%s\n", address, env("INTERNAL_CIDR4"));
-        fprintf(state, "%s\n", routes[0] ? routes : "0.0.0.0/0");
-        fprintf(state, "%s\n", env("INTERNAL_DNS4_LIST"));
-        fprintf(state, "%s\n", env("DEFAULT_DOMAIN"));
-        fprintf(state, "%s\n", env("REMOTE_ADDR"));
-    } else {
-        ALOGE("Cannot parse parameters");
-        fclose(state);
-        return 1;
-    }
-
-    fclose(state);
-    if (chmod(DIR ".tmp", 0444) || rename(DIR ".tmp", DIR "state")) {
-        ALOGE("Cannot write state: %s", strerror(errno));
-        return 1;
-    }
-    return 0;
-}
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index ba8b02d..9d2c791 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -60,6 +60,27 @@
 
 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.
@@ -102,7 +123,7 @@
   *
   * @return APerformanceHintManager instance on success, nullptr on failure.
   */
-APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
@@ -116,9 +137,9 @@
  *     This must be positive if using the work duration API, or 0 otherwise.
  * @return APerformanceHintManager instance on success, nullptr on failure.
  */
-APerformanceHintSession* APerformanceHint_createSession(
-        APerformanceHintManager* manager,
-        const int32_t* threadIds, size_t size,
+APerformanceHintSession* _Nullable APerformanceHint_createSession(
+        APerformanceHintManager* _Nonnull manager,
+        const int32_t* _Nonnull threadIds, size_t size,
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -128,7 +149,7 @@
  * @return the preferred update rate supported by device software.
  */
 int64_t APerformanceHint_getPreferredUpdateRateNanos(
-        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Updates this session's target duration for each cycle of work.
@@ -140,7 +161,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_updateTargetWorkDuration(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull session,
         int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -157,7 +178,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull session,
         int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -167,7 +188,7 @@
  * @param session The performance hint session instance to release.
  */
 void APerformanceHint_closeSession(
-        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintSession* _Nonnull session) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Set a list of threads to the performance hint session. This operation will replace
@@ -184,8 +205,8 @@
  *         EPERM if any thread id doesn't belong to the application.
  */
 int APerformanceHint_setThreads(
-        APerformanceHintSession* session,
-        const pid_t* threadIds,
+        APerformanceHintSession* _Nonnull session,
+        const pid_t* _Nonnull threadIds,
         size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
@@ -198,11 +219,92 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_setPreferPowerEfficiency(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull 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/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 8797962..3b6e401 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <memory>
 #include <mutex>
+#include <optional>
 #include <string>
 #include <unordered_map>
 
@@ -57,20 +58,23 @@
  */
 class MotionPredictor {
 public:
+    using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
     /**
      * Parameters:
      * predictionTimestampOffsetNanos: additional, constant shift to apply to the target
      * prediction time. The prediction will target the time t=(prediction time +
      * predictionTimestampOffsetNanos).
      *
-     * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the
-     * default model path.
-     *
-     * checkEnableMotionPredition: the function to check whether the prediction should run. Used to
+     * checkEnableMotionPrediction: the function to check whether the prediction should run. Used to
      * provide an additional way of turning prediction on and off. Can be toggled at runtime.
+     *
+     * reportAtomFunction: the function that will be called to report prediction metrics. If
+     * omitted, the implementation will choose a default metrics reporting mechanism.
      */
     MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
-                    std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
+                    std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled,
+                    ReportAtomFunction reportAtomFunction = {});
 
     /**
      * Record the actual motion received by the view. This event will be used for calculating the
@@ -95,6 +99,8 @@
     std::optional<MotionEvent> mLastEvent;
 
     std::optional<MotionPredictorMetricsManager> mMetricsManager;
+
+    const ReportAtomFunction mReportAtomFunction;
 };
 
 } // namespace android
diff --git a/include/input/MotionPredictorMetricsManager.h b/include/input/MotionPredictorMetricsManager.h
index 12e50ba..38472d8 100644
--- a/include/input/MotionPredictorMetricsManager.h
+++ b/include/input/MotionPredictorMetricsManager.h
@@ -18,7 +18,6 @@
 #include <cstdint>
 #include <functional>
 #include <limits>
-#include <optional>
 #include <vector>
 
 #include <input/Input.h> // for MotionEvent
@@ -37,15 +36,33 @@
  *
  * This class stores AggregatedStrokeMetrics, updating them as new MotionEvents are passed in. When
  * onRecord receives an UP or CANCEL event, this indicates the end of the stroke, and the final
- * AtomFields are computed and reported to the stats library.
+ * AtomFields are computed and reported to the stats library. The number of atoms reported is equal
+ * to the value of `maxNumPredictions` passed to the constructor. Each atom corresponds to one
+ * "prediction time bucket" — the amount of time into the future being predicted.
  *
  * If mMockLoggedAtomFields is set, the batch of AtomFields that are reported to the stats library
  * for one stroke are also stored in mMockLoggedAtomFields at the time they're reported.
  */
 class MotionPredictorMetricsManager {
 public:
-    // Note: the MetricsManager assumes that the input interval equals the prediction interval.
-    MotionPredictorMetricsManager(nsecs_t predictionInterval, size_t maxNumPredictions);
+    struct AtomFields;
+
+    using ReportAtomFunction = std::function<void(const AtomFields&)>;
+
+    static void defaultReportAtomFunction(const AtomFields& atomFields);
+
+    // Parameters:
+    //  • predictionInterval: the time interval between successive prediction target timestamps.
+    //    Note: the MetricsManager assumes that the input interval equals the prediction interval.
+    //  • maxNumPredictions: the maximum number of distinct target timestamps the prediction model
+    //    will generate predictions for. The MetricsManager reports this many atoms per stroke.
+    //  • [Optional] reportAtomFunction: the function that will be called to report metrics. If
+    //    omitted (or if an empty function is given), the `stats_write(…)` function from the Android
+    //    stats library will be used.
+    MotionPredictorMetricsManager(
+            nsecs_t predictionInterval,
+            size_t maxNumPredictions,
+            ReportAtomFunction reportAtomFunction = defaultReportAtomFunction);
 
     // This method should be called once for each call to MotionPredictor::record, receiving the
     // forwarded MotionEvent argument.
@@ -121,7 +138,7 @@
     // magnitude makes it unobtainable in practice.)
     static const int NO_DATA_SENTINEL = std::numeric_limits<int32_t>::min();
 
-    // Final metrics reported in the atom.
+    // Final metric values reported in the atom.
     struct AtomFields {
         int deltaTimeBucketMilliseconds = 0;
 
@@ -140,15 +157,6 @@
         int scaleInvariantOffTrajectoryRmse = NO_DATA_SENTINEL;   // millipixels
     };
 
-    // Allow tests to pass in a mock AtomFields pointer.
-    //
-    // When metrics are reported to the stats library on stroke end, they will also be written to
-    // mockLoggedAtomFields, overwriting existing data. The size of mockLoggedAtomFields will equal
-    // the number of calls to stats_write for that stroke.
-    void setMockLoggedAtomFields(std::vector<AtomFields>* mockLoggedAtomFields) {
-        mMockLoggedAtomFields = mockLoggedAtomFields;
-    }
-
 private:
     // The interval between consecutive predictions' target timestamps. We assume that the input
     // interval also equals this value.
@@ -172,11 +180,7 @@
     std::vector<AggregatedStrokeMetrics> mAggregatedMetrics;
     std::vector<AtomFields> mAtomFields;
 
-    // Non-owning pointer to the location of mock AtomFields. If present, will be filled with the
-    // values reported to stats_write on each batch of reported metrics.
-    //
-    // This pointer must remain valid as long as the MotionPredictorMetricsManager exists.
-    std::vector<AtomFields>* mMockLoggedAtomFields = nullptr;
+    const ReportAtomFunction mReportAtomFunction;
 
     // Helper methods for the implementation of onRecord and onPredict.
 
@@ -196,10 +200,7 @@
     // Computes the atom fields to mAtomFields from the values in mAggregatedMetrics.
     void computeAtomFields();
 
-    // Reports the metrics given by the current data in mAtomFields:
-    //  • If on an Android device, reports the metrics to stats_write.
-    //  • If mMockLoggedAtomFields is present, it will be overwritten with logged metrics, with one
-    //    AtomFields element per call to stats_write.
+    // Reports the current data in mAtomFields by calling mReportAtomFunction.
     void reportMetrics();
 };
 
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index f90c618..f1cc5f0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -90,8 +90,8 @@
         "Stability.cpp",
         "Status.cpp",
         "TextOutput.cpp",
-        "Trace.cpp",
         "Utils.cpp",
+        "file.cpp",
     ],
 
     shared_libs: [
@@ -106,6 +106,17 @@
     header_libs: [
         "libbinder_headers",
     ],
+
+    cflags: [
+        "-Wextra",
+        "-Wextra-semi",
+        "-Werror",
+        "-Wzero-as-null-pointer-constant",
+        "-Wreorder-init-list",
+        "-Wunused-const-variable",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+    ],
 }
 
 cc_defaults {
@@ -135,15 +146,6 @@
         export_aidl_headers: true,
     },
 
-    cflags: [
-        "-Wextra",
-        "-Wextra-semi",
-        "-Werror",
-        "-Wzero-as-null-pointer-constant",
-        "-Wreorder-init-list",
-        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
-        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
-    ],
     product_variables: {
         debuggable: {
             cflags: [
@@ -215,11 +217,16 @@
     host_supported: true,
 
     header_libs: [
+        "liblog_stub",
         "trusty_mock_headers",
     ],
 
     cflags: [
         "-DBINDER_RPC_SINGLE_THREADED",
+        "-DBINDER_ENABLE_LIBLOG_ASSERT",
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+        "-DBINDER_DISABLE_BLOB",
+        "-DBINDER_NO_LIBBASE",
         // Trusty libbinder uses vendor stability for its binders
         "-D__ANDROID_VNDK__",
         "-U__ANDROID__",
@@ -250,16 +257,12 @@
 
     srcs: [
         // Trusty-specific files
-        "OS_android.cpp",
         "trusty/OS.cpp",
         "trusty/RpcServerTrusty.cpp",
         "trusty/RpcTransportTipcTrusty.cpp",
         "trusty/TrustyStatus.cpp",
         "trusty/socket.cpp",
     ],
-    shared_libs: [
-        "liblog",
-    ],
 }
 
 cc_defaults {
@@ -580,11 +583,6 @@
     ],
 }
 
-filegroup {
-    name: "libbinder_rpc_unstable_header",
-    srcs: ["include_rpc_unstable/binder_rpc_unstable.hpp"],
-}
-
 // libbinder historically contained additional interfaces that provided specific
 // functionality in the platform but have nothing to do with binder itself. These
 // are moved out of libbinder in order to avoid the overhead of their vtables.
@@ -653,4 +651,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..0035aeb 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -18,13 +18,16 @@
 #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);
+void trace_begin(uint64_t tag, const char* name);
+void trace_end(uint64_t tag);
+
+status_t setNonBlocking(borrowed_fd fd);
 
 status_t getRandomBytes(uint8_t* data, size_t size);
 
@@ -32,13 +35,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_android.cpp b/libs/binder/OS_android.cpp
index ad458eb..1eace85 100644
--- a/libs/binder/OS_android.cpp
+++ b/libs/binder/OS_android.cpp
@@ -17,9 +17,11 @@
 #include "OS.h"
 
 #include <android-base/threads.h>
+#include <cutils/trace.h>
 #include <utils/misc.h>
 
-namespace android::binder::os {
+namespace android::binder {
+namespace os {
 
 uint64_t GetThreadId() {
 #ifdef BINDER_RPC_SINGLE_THREADED
@@ -34,4 +36,24 @@
     return true;
 }
 
-} // namespace android::binder::os
+void trace_begin(uint64_t tag, const char* name) {
+    atrace_begin(tag, name);
+}
+
+void trace_end(uint64_t tag) {
+    atrace_end(tag);
+}
+
+} // namespace os
+
+// Legacy trace symbol. To be removed once all of downstream rebuilds.
+void atrace_begin(uint64_t tag, const char* name) {
+    os::trace_begin(tag, name);
+}
+
+// Legacy trace symbol. To be removed once all of downstream rebuilds.
+void atrace_end(uint64_t tag) {
+    os::trace_end(tag);
+}
+
+} // namespace android::binder
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..c1770b3 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__
@@ -112,7 +114,7 @@
 constexpr size_t kMaxFds = 1024;
 
 // Maximum size of a blob to transfer in-place.
-static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
+[[maybe_unused]] static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
 
 #if defined(__BIONIC__)
 static void FdTag(int fd, const void* old_addr, const void* new_addr) {
@@ -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) {
@@ -884,6 +886,9 @@
 }
 
 #ifdef BINDER_WITH_KERNEL_IPC
+
+#if defined(__ANDROID__)
+
 #if defined(__ANDROID_VNDK__)
 constexpr int32_t kHeader = B_PACK_CHARS('V', 'N', 'D', 'R');
 #elif defined(__ANDROID_RECOVERY__)
@@ -891,6 +896,14 @@
 #else
 constexpr int32_t kHeader = B_PACK_CHARS('S', 'Y', 'S', 'T');
 #endif
+
+#else // ANDROID not defined
+
+// If kernel binder is used in new environments, we need to make sure it's separated
+// out and has a separate header.
+constexpr int32_t kHeader = B_PACK_CHARS('U', 'N', 'K', 'N');
+#endif
+
 #endif // BINDER_WITH_KERNEL_IPC
 
 // Write RPC headers.  (previously just the interface token)
@@ -1247,9 +1260,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 +1322,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 +1531,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 +1614,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 +2417,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 +2438,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 +2735,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/Trace.cpp b/libs/binder/Trace.cpp
deleted file mode 100644
index 1ebfa1a..0000000
--- a/libs/binder/Trace.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <binder/Trace.h>
-#include <cutils/trace.h>
-
-namespace android {
-namespace binder {
-
-void atrace_begin(uint64_t tag, const char* name) {
-    ::atrace_begin(tag, name);
-}
-
-void atrace_end(uint64_t tag) {
-    ::atrace_end(tag);
-}
-
-} // namespace binder
-} // namespace android
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..6e450b9
--- /dev/null
+++ b/libs/binder/file.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "Utils.h"
+
+#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/Delegate.h b/libs/binder/include/binder/Delegate.h
index 8b3fc1c..ad5a6a3 100644
--- a/libs/binder/include/binder/Delegate.h
+++ b/libs/binder/include/binder/Delegate.h
@@ -18,6 +18,11 @@
 
 #include <binder/IBinder.h>
 
+#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
+#include <log/log.h>
+#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
+#endif
+
 #ifndef __BIONIC__
 #ifndef __assert
 
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/Trace.h b/libs/binder/include/binder/Trace.h
index 9937842..95318b2 100644
--- a/libs/binder/include/binder/Trace.h
+++ b/libs/binder/include/binder/Trace.h
@@ -16,22 +16,36 @@
 
 #pragma once
 
-#include <cutils/trace.h>
 #include <stdint.h>
 
+#if __has_include(<cutils/trace.h>)
+#include <cutils/trace.h>
+#endif
+
+#ifdef ATRACE_TAG_AIDL
+#if ATRACE_TAG_AIDL != (1 << 24)
+#error "Mismatched ATRACE_TAG_AIDL definitions"
+#endif
+#else
+#define ATRACE_TAG_AIDL (1 << 24)
+#endif
+
 namespace android {
 namespace binder {
 
+// Forward declarations from internal OS.h
+namespace os {
 // Trampoline functions allowing generated aidls to trace binder transactions without depending on
 // libcutils/libutils
-void atrace_begin(uint64_t tag, const char* name);
-void atrace_end(uint64_t tag);
+void trace_begin(uint64_t tag, const char* name);
+void trace_end(uint64_t tag);
+} // namespace os
 
 class ScopedTrace {
 public:
-    inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { atrace_begin(mTag, name); }
+    inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { os::trace_begin(mTag, name); }
 
-    inline ~ScopedTrace() { atrace_end(mTag); }
+    inline ~ScopedTrace() { os::trace_end(mTag); }
 
 private:
     uint64_t mTag;
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/liblog_stub/Android.bp b/libs/binder/liblog_stub/Android.bp
new file mode 100644
index 0000000..f2ca22f
--- /dev/null
+++ b/libs/binder/liblog_stub/Android.bp
@@ -0,0 +1,44 @@
+// 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.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_headers {
+    name: "liblog_stub",
+    export_include_dirs: ["include"],
+
+    host_supported: true,
+    native_bridge_supported: true,
+    product_available: true,
+    recovery_available: true,
+    vendor_available: true,
+
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
+
+    visibility: [
+        "//frameworks/native/libs/binder:__subpackages__",
+        "//system/core/libutils/binder",
+    ],
+}
diff --git a/libs/binder/liblog_stub/include/android/log.h b/libs/binder/liblog_stub/include/android/log.h
new file mode 100644
index 0000000..9dcd926
--- /dev/null
+++ b/libs/binder/liblog_stub/include/android/log.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
+
+extern "C" {
+
+/**
+ * Android log priority values, in increasing order of priority.
+ */
+typedef enum android_LogPriority {
+    /** For internal use only.  */
+    ANDROID_LOG_UNKNOWN = 0,
+    /** The default priority, for internal use only.  */
+    ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
+    /** Verbose logging. Should typically be disabled for a release apk. */
+    ANDROID_LOG_VERBOSE,
+    /** Debug logging. Should typically be disabled for a release apk. */
+    ANDROID_LOG_DEBUG,
+    /** Informational logging. Should typically be disabled for a release apk. */
+    ANDROID_LOG_INFO,
+    /** Warning logging. For use with recoverable failures. */
+    ANDROID_LOG_WARN,
+    /** Error logging. For use with unrecoverable failures. */
+    ANDROID_LOG_ERROR,
+    /** Fatal logging. For use when aborting. */
+    ANDROID_LOG_FATAL,
+    /** For internal use only.  */
+    ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
+} android_LogPriority;
+
+typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
+inline void __android_log_set_logger(__android_logger_function) {}
+inline void __android_log_stderr_logger(const struct __android_log_message*) {}
+
+} // extern "C"
diff --git a/libs/binder/liblog_stub/include/log/log.h b/libs/binder/liblog_stub/include/log/log.h
new file mode 100644
index 0000000..3b65607
--- /dev/null
+++ b/libs/binder/liblog_stub/include/log/log.h
@@ -0,0 +1,89 @@
+/*
+ * 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 <cstdio>
+#include <cstdlib>
+
+#include <android/log.h>
+
+extern "C" {
+
+#ifndef ANDROID_LOG_STUB_MIN_PRIORITY
+#define ANDROID_LOG_STUB_MIN_PRIORITY ANDROID_LOG_INFO
+#endif
+
+#ifndef LOG_TAG
+#define LOG_TAG ""
+#endif
+
+constexpr bool __android_log_stub_is_loggable(android_LogPriority priority) {
+    return ANDROID_LOG_STUB_MIN_PRIORITY <= priority;
+}
+
+int __android_log_print(int prio, const char* tag, const char* fmt, ...)
+        __attribute__((format(printf, 3, 4)))
+#ifdef ANDROID_LOG_STUB_WEAK_PRINT
+        __attribute__((weak))
+#endif
+        ;
+
+#define IF_ALOG(priority, tag) \
+    if (__android_log_stub_is_loggable(ANDROID_##priority) && __android_log_print)
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+
+#define ALOG(priority, tag, fmt, ...)                                          \
+    do {                                                                       \
+        if (false)[[/*VERY*/ unlikely]] { /* ignore unused __VA_ARGS__ */      \
+            std::fprintf(stderr, fmt __VA_OPT__(, ) __VA_ARGS__);              \
+        }                                                                      \
+        IF_ALOG(priority, tag) {                                               \
+            __android_log_print(ANDROID_##priority, tag,                       \
+                                tag ": " fmt "\n" __VA_OPT__(, ) __VA_ARGS__); \
+        }                                                                      \
+        if constexpr (ANDROID_##priority == ANDROID_LOG_FATAL) std::abort();   \
+    } while (false)
+#define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL(...) ALOG(LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL LOG_FATAL
+
+#define ALOG_IF(cond, priority, tag, ...) \
+    if (cond) [[unlikely]]                \
+    ALOG(priority, tag, #cond ": " __VA_ARGS__)
+#define ALOGV_IF(cond, ...) ALOG_IF(cond, LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD_IF(cond, ...) ALOG_IF(cond, LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI_IF(cond, ...) ALOG_IF(cond, LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW_IF(cond, ...) ALOG_IF(cond, LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE_IF(cond, ...) ALOG_IF(cond, LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL_IF(cond, ...) ALOG_IF(cond, LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL_IF LOG_FATAL_IF
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+
+inline int android_errorWriteLog(int tag, const char* subTag) {
+    ALOGE("android_errorWriteLog(%x, %s)", tag, subTag);
+    return 0;
+}
+
+} // extern "C"
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 47b9f58..ccf3ce8 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -139,6 +139,7 @@
         "performance*",
         "portability*",
     ],
+    afdo: true,
 }
 
 cc_library_headers {
diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h
index 4786c89..14edf2b 100644
--- a/libs/binder/ndk/include_ndk/android/binder_status.h
+++ b/libs/binder/ndk/include_ndk/android/binder_status.h
@@ -31,6 +31,11 @@
 #include <stdint.h>
 #include <sys/cdefs.h>
 
+#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
+#include <log/log.h>
+#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
+#endif
+
 __BEGIN_DECLS
 
 #ifndef __BIONIC__
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index c1f62e5..089c775 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -21,17 +21,15 @@
 __BEGIN_DECLS
 
 /**
- * Private addition to binder_flag_t.
+ * Indicates that this transaction is coupled w/ vendor.img
  */
-enum {
-    /**
-     * Indicates that this transaction is coupled w/ vendor.img
-     */
-    FLAG_PRIVATE_VENDOR = 0x10000000,
-};
+constexpr binder_flags_t FLAG_PRIVATE_VENDOR = 0x10000000;
 
 #if defined(__ANDROID_VENDOR__)
 
+/**
+ * Private addition to binder_flag_t.
+ */
 enum {
     FLAG_PRIVATE_LOCAL = FLAG_PRIVATE_VENDOR,
 };
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/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 788abc4..535ce01 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -70,7 +70,7 @@
 // TODO(b/184872979): remove once the RPC Binder API is stabilised.
 rust_bindgen {
     name: "libbinder_rpc_unstable_bindgen",
-    wrapper_src: ":libbinder_rpc_unstable_header",
+    wrapper_src: "BinderBindings.hpp",
     crate_name: "binder_rpc_unstable_bindgen",
     visibility: [":__subpackages__"],
     source_stem: "bindings",
diff --git a/libs/binder/rust/rpcbinder/BinderBindings.hpp b/libs/binder/rust/rpcbinder/BinderBindings.hpp
new file mode 100644
index 0000000..7feb965
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/BinderBindings.hpp
@@ -0,0 +1 @@
+#include <binder_rpc_unstable.hpp>
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 6d122c5..e34d31e 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -30,8 +30,8 @@
 use std::io::Write;
 use std::marker::PhantomData;
 use std::ops::Deref;
+use std::os::fd::AsRawFd;
 use std::os::raw::c_char;
-use std::os::unix::io::AsRawFd;
 use std::ptr;
 
 /// Binder action to perform.
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index ed870b6..7f9348d 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,6 +100,7 @@
 mod native;
 mod parcel;
 mod proxy;
+#[cfg(not(target_os = "trusty"))]
 mod state;
 
 use binder_ndk_sys as sys;
@@ -116,6 +117,7 @@
     get_declared_instances, get_interface, get_service, is_declared, wait_for_interface,
     wait_for_service, DeathRecipient, SpIBinder, WpIBinder,
 };
+#[cfg(not(target_os = "trusty"))]
 pub use state::{ProcessState, ThreadState};
 
 /// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index b250012..8ae010e 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -24,13 +24,10 @@
 
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
-use std::fs::File;
 use std::io::Write;
 use std::mem::ManuallyDrop;
 use std::ops::Deref;
 use std::os::raw::c_char;
-use std::os::unix::io::FromRawFd;
-use std::slice;
 use std::sync::Mutex;
 
 /// Rust wrapper around Binder remotable objects.
@@ -331,6 +328,7 @@
     /// contains a `T` pointer in its user data. fd should be a non-owned file
     /// descriptor, and args must be an array of null-terminated string
     /// pointers with length num_args.
+    #[cfg(not(target_os = "trusty"))]
     unsafe extern "C" fn on_dump(
         binder: *mut sys::AIBinder,
         fd: i32,
@@ -340,9 +338,10 @@
         if fd < 0 {
             return StatusCode::UNEXPECTED_NULL as status_t;
         }
+        use std::os::fd::FromRawFd;
         // Safety: Our caller promised that fd is a file descriptor. We don't
         // own this file descriptor, so we need to be careful not to drop it.
-        let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
+        let mut file = unsafe { ManuallyDrop::new(std::fs::File::from_raw_fd(fd)) };
 
         if args.is_null() && num_args != 0 {
             return StatusCode::UNEXPECTED_NULL as status_t;
@@ -354,7 +353,7 @@
             // Safety: Our caller promised that `args` is an array of
             // null-terminated string pointers with length `num_args`.
             unsafe {
-                slice::from_raw_parts(args, num_args as usize)
+                std::slice::from_raw_parts(args, num_args as usize)
                     .iter()
                     .map(|s| CStr::from_ptr(*s))
                     .collect()
@@ -374,6 +373,19 @@
             Err(e) => e as status_t,
         }
     }
+
+    /// Called to handle the `dump` transaction.
+    #[cfg(target_os = "trusty")]
+    unsafe extern "C" fn on_dump(
+        _binder: *mut sys::AIBinder,
+        _fd: i32,
+        _args: *mut *const c_char,
+        _num_args: u32,
+    ) -> status_t {
+        // This operation is not supported on Trusty right now
+        // because we do not have a uniform way of writing to handles
+        StatusCode::INVALID_OPERATION as status_t
+    }
 }
 
 impl<T: Remotable> Drop for Binder<T> {
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index dad3379..7434e9d 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -32,8 +32,8 @@
 use std::ffi::{c_void, CStr, CString};
 use std::fmt;
 use std::mem;
+use std::os::fd::AsRawFd;
 use std::os::raw::c_char;
-use std::os::unix::io::AsRawFd;
 use std::ptr;
 use std::sync::Arc;
 
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..e4d4de8 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -134,6 +134,10 @@
         "IBinderRpcTest.aidl",
         "ParcelableCertificateData.aidl",
     ],
+    flags: [
+        "-Werror",
+        "-Wno-mixed-oneway",
+    ],
     backend: {
         java: {
             enabled: false,
@@ -172,6 +176,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 +213,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..62fe9e5 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));
@@ -392,7 +393,7 @@
         }
 
         if (delayed) {
-            RpcMaybeThread([=]() {
+            RpcMaybeThread([=, this]() {
                 ALOGE("Executing delayed callback: '%s'", value.c_str());
                 Status status = doCallback(callback, oneway, false, value);
                 ALOGE("Delayed callback status: '%s'", status.toString8().c_str());
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/binderRpcTestServiceTrusty.cpp b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
index aaca8d0..8346b36 100644
--- a/libs/binder/tests/binderRpcTestServiceTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
@@ -77,16 +77,14 @@
         // Message size needs to be large enough to cover all messages sent by the
         // tests: SendAndGetResultBackBig sends two large strings.
         constexpr size_t max_msg_size = 4096;
-        auto serverOrErr =
+        auto server =
                 RpcServerTrusty::make(hset, serverInfo.port->c_str(),
                                       std::shared_ptr<const RpcServerTrusty::PortAcl>(&port_acl),
                                       max_msg_size);
-        if (!serverOrErr.ok()) {
-            TLOGE("Failed to create RpcServer (%d)\n", serverOrErr.error());
+        if (server == nullptr) {
             return EXIT_FAILURE;
         }
 
-        auto server = std::move(*serverOrErr);
         serverInfo.server = server;
         if (!serverInfo.server->setProtocolVersion(serverVersion)) {
             return EXIT_FAILURE;
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/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index fe79f8e..83db6c9 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -16,6 +16,9 @@
         "parcelables/SingleDataParcelable.aidl",
         "parcelables/GenericDataParcelable.aidl",
     ],
+    flags: [
+        "-Werror",
+    ],
     backend: {
         java: {
             enabled: true,
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/parcelables/GenericDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
index dd08f72..9884dbb 100644
--- a/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
@@ -17,7 +17,7 @@
 
 parcelable GenericDataParcelable {
     enum JustSomeEnum {
-        SOME_ENUMERATOR,
+        ONE_ENUMERATOR,
         ANOTHER_ENUMERATOR,
         MAYBE_ONE_MORE_ENUMERATOR,
     }
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..9869bf3 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -22,13 +22,32 @@
 #endif
 
 #include <binder/RpcTransportTipcTrusty.h>
+#include <log/log.h>
+#include <trusty_log.h>
 
 #include "../OS.h"
 #include "TrustyStatus.h"
 
+#include <cstdarg>
+
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
+
 namespace android::binder::os {
 
-status_t setNonBlocking(android::base::borrowed_fd /*fd*/) {
+void trace_begin(uint64_t, const char*) {}
+
+void trace_end(uint64_t) {}
+
+uint64_t GetThreadId() {
+    return 0;
+}
+
+bool report_sysprop_change() {
+    return false;
+}
+
+status_t setNonBlocking(borrowed_fd /*fd*/) {
     // Trusty IPC syscalls are all non-blocking by default.
     return OK;
 }
@@ -59,16 +78,56 @@
 
 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;
 }
 
 } // namespace android::binder::os
+
+int __android_log_print(int prio [[maybe_unused]], const char* tag, const char* fmt, ...) {
+#ifdef TRUSTY_USERSPACE
+#define trusty_tlog _tlog
+#define trusty_vtlog _vtlog
+#else
+    // mapping taken from kernel trusty_log.h (TLOGx)
+    int kernelLogLevel;
+    if (prio <= ANDROID_LOG_DEBUG) {
+        kernelLogLevel = LK_DEBUGLEVEL_ALWAYS;
+    } else if (prio == ANDROID_LOG_INFO) {
+        kernelLogLevel = LK_DEBUGLEVEL_SPEW;
+    } else if (prio == ANDROID_LOG_WARN) {
+        kernelLogLevel = LK_DEBUGLEVEL_INFO;
+    } else if (prio == ANDROID_LOG_ERROR) {
+        kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+    } else { /* prio >= ANDROID_LOG_FATAL */
+        kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+    }
+#if LK_DEBUGLEVEL_NO_ALIASES
+    auto LK_DEBUGLEVEL_kernelLogLevel = kernelLogLevel;
+#endif
+
+#define trusty_tlog(...) _tlog(kernelLogLevel, __VA_ARGS__)
+#define trusty_vtlog(...) _vtlog(kernelLogLevel, __VA_ARGS__)
+#endif
+
+    va_list args;
+    va_start(args, fmt);
+    trusty_tlog((tag[0] == '\0') ? "libbinder" : "libbinder-");
+    trusty_vtlog(fmt, args);
+    va_end(args);
+
+    return 1;
+}
+
+// TODO(b/285204695): remove once trusty mock doesn't depend on libbase
+extern "C" int __android_log_buf_print(int, int, const char*, const char*, ...) {
+    return -ENOSYS;
+}
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 8f64323..1f857a0 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -27,11 +27,11 @@
 #include "../RpcState.h"
 #include "TrustyStatus.h"
 
-using android::base::unexpected;
+using android::binder::unique_fd;
 
 namespace android {
 
-android::base::expected<sp<RpcServerTrusty>, int> RpcServerTrusty::make(
+sp<RpcServerTrusty> RpcServerTrusty::make(
         tipc_hset* handleSet, std::string&& portName, std::shared_ptr<const PortAcl>&& portAcl,
         size_t msgMaxSize, std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
     // Default is without TLS.
@@ -39,18 +39,21 @@
         rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
     auto ctx = rpcTransportCtxFactory->newServerCtx();
     if (ctx == nullptr) {
-        return unexpected(ERR_NO_MEMORY);
+        ALOGE("Failed to create RpcServerTrusty: can't create server context");
+        return nullptr;
     }
 
     auto srv = sp<RpcServerTrusty>::make(std::move(ctx), std::move(portName), std::move(portAcl),
                                          msgMaxSize);
     if (srv == nullptr) {
-        return unexpected(ERR_NO_MEMORY);
+        ALOGE("Failed to create RpcServerTrusty: can't create server object");
+        return nullptr;
     }
 
     int rc = tipc_add_service(handleSet, &srv->mTipcPort, 1, 0, &kTipcOps);
     if (rc != NO_ERROR) {
-        return unexpected(rc);
+        ALOGE("Failed to create RpcServerTrusty: can't add service: %d", rc);
+        return nullptr;
     }
     return srv;
 }
@@ -129,7 +132,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/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
index 1c4f7ee..d2a1fc0 100644
--- a/libs/binder/trusty/binderRpcTest/service/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -2,7 +2,7 @@
     "uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
     "app_name": "binderRpcTestService",
     "min_heap": 65536,
-    "min_stack": 16384,
+    "min_stack": 20480,
     "mgmt_flags": {
         "restart_on_exit": true,
         "non_critical_app": true
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index aa476f9..f35d6c2 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,12 +16,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>
 
@@ -53,7 +52,7 @@
      * The caller is responsible for calling tipc_run_event_loop() to start
      * the TIPC event loop after creating one or more services here.
      */
-    static android::base::expected<sp<RpcServerTrusty>, int> make(
+    static sp<RpcServerTrusty> make(
             tipc_hset* handleSet, std::string&& portName, std::shared_ptr<const PortAcl>&& portAcl,
             size_t msgMaxSize,
             std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
diff --git a/libs/binder/trusty/include/log/log.h b/libs/binder/trusty/include/log/log.h
deleted file mode 100644
index de84617..0000000
--- a/libs/binder/trusty/include/log/log.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#define BINDER_LOG_LEVEL_NONE 0
-#define BINDER_LOG_LEVEL_NORMAL 1
-#define BINDER_LOG_LEVEL_VERBOSE 2
-
-#ifndef BINDER_LOG_LEVEL
-#define BINDER_LOG_LEVEL BINDER_LOG_LEVEL_NORMAL
-#endif // BINDER_LOG_LEVEL
-
-#ifndef TLOG_TAG
-#ifdef LOG_TAG
-#define TLOG_TAG "libbinder-" LOG_TAG
-#else // LOG_TAG
-#define TLOG_TAG "libbinder"
-#endif // LOG_TAG
-#endif // TLOG_TAG
-
-#include <stdlib.h>
-#include <trusty_log.h>
-
-static inline void __ignore_va_args__(...) {}
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...) TLOGD(fmt "\n", ##__VA_ARGS__)
-#define ALOGI(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#define ALOGW(fmt, ...) TLOGW(fmt "\n", ##__VA_ARGS__)
-#define ALOGE(fmt, ...) TLOGE(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...)                  \
-    while (0) {                          \
-        __ignore_va_args__(__VA_ARGS__); \
-    }
-#define ALOGI(fmt, ...)                  \
-    while (0) {                          \
-        __ignore_va_args__(__VA_ARGS__); \
-    }
-#define ALOGW(fmt, ...)                  \
-    while (0) {                          \
-        __ignore_va_args__(__VA_ARGS__); \
-    }
-#define ALOGE(fmt, ...)                  \
-    while (0) {                          \
-        __ignore_va_args__(__VA_ARGS__); \
-    }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (TLOG_LVL >= TLOG_LVL_INFO)
-#define ALOGV(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (false)
-#define ALOGV(fmt, ...)                  \
-    while (0) {                          \
-        __ignore_va_args__(__VA_ARGS__); \
-    }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-
-#define ALOGI_IF(cond, ...)                \
-    do {                                   \
-        if (cond) {                        \
-            ALOGI(#cond ": " __VA_ARGS__); \
-        }                                  \
-    } while (0)
-#define ALOGE_IF(cond, ...)                \
-    do {                                   \
-        if (cond) {                        \
-            ALOGE(#cond ": " __VA_ARGS__); \
-        }                                  \
-    } while (0)
-#define ALOGW_IF(cond, ...)                \
-    do {                                   \
-        if (cond) {                        \
-            ALOGW(#cond ": " __VA_ARGS__); \
-        }                                  \
-    } while (0)
-
-#define LOG_ALWAYS_FATAL(fmt, ...)                                \
-    do {                                                          \
-        TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
-        abort();                                                  \
-    } while (0)
-#define LOG_ALWAYS_FATAL_IF(cond, ...)                \
-    do {                                              \
-        if (cond) {                                   \
-            LOG_ALWAYS_FATAL(#cond ": " __VA_ARGS__); \
-        }                                             \
-    } while (0)
-#define LOG_FATAL(fmt, ...)                                       \
-    do {                                                          \
-        TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
-        abort();                                                  \
-    } while (0)
-#define LOG_FATAL_IF(cond, ...)                \
-    do {                                       \
-        if (cond) {                            \
-            LOG_FATAL(#cond ": " __VA_ARGS__); \
-        }                                      \
-    } while (0)
-
-#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
-
-#define android_errorWriteLog(tag, subTag)                               \
-    do {                                                                 \
-        TLOGE("android_errorWriteLog: tag:%x subTag:%s\n", tag, subTag); \
-    } while (0)
-
-// Override the definition of __assert from binder_status.h
-#ifndef __BIONIC__
-#undef __assert
-#define __assert(file, line, str) LOG_ALWAYS_FATAL("%s:%d: %s", file, line, str)
-#endif // __BIONIC__
diff --git a/libs/binder/trusty/include_mock/trusty_log.h b/libs/binder/trusty/include_mock/trusty_log.h
index d51e752..9aa9031 100644
--- a/libs/binder/trusty/include_mock/trusty_log.h
+++ b/libs/binder/trusty/include_mock/trusty_log.h
@@ -24,3 +24,6 @@
 #define TLOGW(fmt, ...) printf(fmt, ##__VA_ARGS__)
 #define TLOGE(fmt, ...) printf(fmt, ##__VA_ARGS__)
 #define TLOGC(fmt, ...) printf(fmt, ##__VA_ARGS__)
+
+#define _tlog(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#define _vtlog(fmt, args) vprintf(fmt, args)
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index d2b37aa..788184d 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -18,34 +18,32 @@
 MODULE := $(LOCAL_DIR)
 
 LIBBINDER_DIR := frameworks/native/libs/binder
+# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
 LIBBASE_DIR := system/libbase
-LIBCUTILS_DIR := system/core/libcutils
-LIBUTILS_DIR := system/core/libutils
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
+LIBUTILS_BINDER_DIR := system/core/libutils/binder
 FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
+	$(LOCAL_DIR)/../OS.cpp \
 	$(LOCAL_DIR)/../TrustyStatus.cpp \
 	$(LIBBINDER_DIR)/Binder.cpp \
 	$(LIBBINDER_DIR)/BpBinder.cpp \
 	$(LIBBINDER_DIR)/FdTrigger.cpp \
 	$(LIBBINDER_DIR)/IInterface.cpp \
 	$(LIBBINDER_DIR)/IResultReceiver.cpp \
-	$(LIBBINDER_DIR)/OS_android.cpp \
 	$(LIBBINDER_DIR)/Parcel.cpp \
 	$(LIBBINDER_DIR)/Stability.cpp \
 	$(LIBBINDER_DIR)/Status.cpp \
 	$(LIBBINDER_DIR)/Utils.cpp \
-	$(LIBBASE_DIR)/hex.cpp \
-	$(LIBBASE_DIR)/stringprintf.cpp \
-	$(LIBUTILS_DIR)/binder/Errors.cpp \
-	$(LIBUTILS_DIR)/binder/RefBase.cpp \
-	$(LIBUTILS_DIR)/binder/SharedBuffer.cpp \
-	$(LIBUTILS_DIR)/binder/String16.cpp \
-	$(LIBUTILS_DIR)/binder/String8.cpp \
-	$(LIBUTILS_DIR)/binder/StrongPointer.cpp \
-	$(LIBUTILS_DIR)/binder/Unicode.cpp \
-	$(LIBUTILS_DIR)/binder/VectorImpl.cpp \
-	$(LIBUTILS_DIR)/misc.cpp \
+	$(LIBUTILS_BINDER_DIR)/Errors.cpp \
+	$(LIBUTILS_BINDER_DIR)/RefBase.cpp \
+	$(LIBUTILS_BINDER_DIR)/SharedBuffer.cpp \
+	$(LIBUTILS_BINDER_DIR)/String16.cpp \
+	$(LIBUTILS_BINDER_DIR)/String8.cpp \
+	$(LIBUTILS_BINDER_DIR)/StrongPointer.cpp \
+	$(LIBUTILS_BINDER_DIR)/Unicode.cpp \
+	$(LIBUTILS_BINDER_DIR)/VectorImpl.cpp \
 
 MODULE_DEFINES += \
 	LK_DEBUGLEVEL_NO_ALIASES=1 \
@@ -56,17 +54,21 @@
 GLOBAL_INCLUDES += \
 	$(LOCAL_DIR)/include \
 	$(LOCAL_DIR)/../include \
+	$(LIBLOG_STUB_DIR)/include \
 	$(LIBBINDER_DIR)/include \
 	$(LIBBINDER_DIR)/ndk/include_cpp \
 	$(LIBBASE_DIR)/include \
-	$(LIBCUTILS_DIR)/include \
-	$(LIBUTILS_DIR)/include \
+	$(LIBUTILS_BINDER_DIR)/include \
 	$(FMTLIB_DIR)/include \
 
 GLOBAL_COMPILEFLAGS += \
 	-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION \
 	-DBINDER_NO_KERNEL_IPC \
 	-DBINDER_RPC_SINGLE_THREADED \
+	-DBINDER_ENABLE_LIBLOG_ASSERT \
+	-DBINDER_DISABLE_NATIVE_HANDLE \
+	-DBINDER_DISABLE_BLOB \
+	-DBINDER_NO_LIBBASE \
 	-D__ANDROID_VNDK__ \
 
 MODULE_DEPS += \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 9cad556..e0f821f 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -18,9 +18,10 @@
 MODULE := $(LOCAL_DIR)
 
 LIBBINDER_DIR := frameworks/native/libs/binder
+# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
 LIBBASE_DIR := system/libbase
-LIBCUTILS_DIR := system/core/libcutils
-LIBUTILS_DIR := system/core/libutils
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
+LIBUTILS_BINDER_DIR := system/core/libutils/binder
 FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
@@ -34,7 +35,6 @@
 	$(LIBBINDER_DIR)/FdTrigger.cpp \
 	$(LIBBINDER_DIR)/IInterface.cpp \
 	$(LIBBINDER_DIR)/IResultReceiver.cpp \
-	$(LIBBINDER_DIR)/OS_android.cpp \
 	$(LIBBINDER_DIR)/Parcel.cpp \
 	$(LIBBINDER_DIR)/ParcelFileDescriptor.cpp \
 	$(LIBBINDER_DIR)/RpcServer.cpp \
@@ -43,24 +43,22 @@
 	$(LIBBINDER_DIR)/Stability.cpp \
 	$(LIBBINDER_DIR)/Status.cpp \
 	$(LIBBINDER_DIR)/Utils.cpp \
-	$(LIBBASE_DIR)/hex.cpp \
-	$(LIBBASE_DIR)/stringprintf.cpp \
-	$(LIBUTILS_DIR)/binder/Errors.cpp \
-	$(LIBUTILS_DIR)/binder/RefBase.cpp \
-	$(LIBUTILS_DIR)/binder/SharedBuffer.cpp \
-	$(LIBUTILS_DIR)/binder/String16.cpp \
-	$(LIBUTILS_DIR)/binder/String8.cpp \
-	$(LIBUTILS_DIR)/binder/StrongPointer.cpp \
-	$(LIBUTILS_DIR)/binder/Unicode.cpp \
-	$(LIBUTILS_DIR)/binder/VectorImpl.cpp \
-	$(LIBUTILS_DIR)/misc.cpp \
+	$(LIBBINDER_DIR)/file.cpp \
+	$(LIBUTILS_BINDER_DIR)/Errors.cpp \
+	$(LIBUTILS_BINDER_DIR)/RefBase.cpp \
+	$(LIBUTILS_BINDER_DIR)/SharedBuffer.cpp \
+	$(LIBUTILS_BINDER_DIR)/String16.cpp \
+	$(LIBUTILS_BINDER_DIR)/String8.cpp \
+	$(LIBUTILS_BINDER_DIR)/StrongPointer.cpp \
+	$(LIBUTILS_BINDER_DIR)/Unicode.cpp \
+	$(LIBUTILS_BINDER_DIR)/VectorImpl.cpp \
 
 MODULE_EXPORT_INCLUDES += \
 	$(LOCAL_DIR)/include \
+	$(LIBLOG_STUB_DIR)/include \
 	$(LIBBINDER_DIR)/include \
 	$(LIBBASE_DIR)/include \
-	$(LIBCUTILS_DIR)/include \
-	$(LIBUTILS_DIR)/include \
+	$(LIBUTILS_BINDER_DIR)/include \
 	$(FMTLIB_DIR)/include \
 
 # The android/binder_to_string.h header is shared between libbinder and
@@ -70,6 +68,10 @@
 
 MODULE_EXPORT_COMPILEFLAGS += \
 	-DBINDER_RPC_SINGLE_THREADED \
+	-DBINDER_ENABLE_LIBLOG_ASSERT \
+	-DBINDER_DISABLE_NATIVE_HANDLE \
+	-DBINDER_DISABLE_BLOB \
+	-DBINDER_NO_LIBBASE \
 	-D__ANDROID_VNDK__ \
 
 # libbinder has some deprecated declarations that we want to produce warnings
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
new file mode 100644
index 0000000..be90df3
--- /dev/null
+++ b/libs/binder/trusty/rust/rules.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LIBBINDER_DIR)/rust/src/lib.rs
+
+MODULE_CRATE_NAME := binder
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+	external/rust/crates/downcast-rs \
+	trusty/user/base/lib/trusty-sys \
+
+# Trusty does not have `ProcessState`, so there are a few
+# doc links in `IBinder` that are still broken.
+MODULE_RUSTFLAGS += \
+	--allow rustdoc::broken-intra-doc-links \
+
+include make/library.mk
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
index d6305f8..bb573c5 100644
--- a/libs/bufferstreams/examples/app/Android.bp
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -23,6 +23,9 @@
     kotlincflags: [
         "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
     ],
+    optimize: {
+        proguard_flags_files: ["proguard-rules.pro"],
+    },
 
     resource_dirs: ["res"],
 
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
index a2db934..ede7793 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -10,18 +10,18 @@
      * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
      * packaged with this application.
      */
-    external fun stringFromJNI()
-    external fun testBufferQueueCreation()
+    external fun stringFromJNI(): String;
+    external fun testBufferQueueCreation();
 
     companion object {
-        fun companion_stringFromJNI() {
+        fun companion_stringFromJNI(): String {
             val instance = BufferStreamJNI()
-            instance.stringFromJNI()
+            return instance.stringFromJNI()
         }
 
         fun companion_testBufferQueueCreation() {
             val instance = BufferStreamJNI()
-            instance.testBufferQueueCreation()
+            return instance.testBufferQueueCreation()
         }
     }
 }
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
index 46ce028..95e415e 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -4,10 +4,8 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Button
-import androidx.compose.material3.Card
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -17,17 +15,21 @@
 @Composable
 fun DemoScreen1(modifier: Modifier = Modifier) {
     Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
-        Card(modifier = Modifier.fillMaxWidth().weight(1f, false).padding(16.dp).height(400.dp)) {
-            Text("Log output", modifier = Modifier.padding(16.dp))
-        }
+        LogOutput.getInstance().LogOutputComposable()
         Row(modifier = Modifier.weight(1f, false).padding(16.dp)) {
             Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
                 Button(
-                        modifier = Modifier.fillMaxWidth(),
-                        onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }
-                ) { Text("Run") }
-                OutlinedButton(modifier = Modifier.fillMaxWidth(), onClick = {}) { Text("Clear") }
+                    modifier = Modifier.fillMaxWidth(),
+                    onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) {
+                        Text("Run")
+                    }
+
+                OutlinedButton(
+                    modifier = Modifier.fillMaxWidth(),
+                    onClick = { LogOutput.getInstance().clearText() }) {
+                        Text("Clear")
+                    }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
new file mode 100644
index 0000000..3f0926f
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
@@ -0,0 +1,65 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import java.util.Collections
+
+/*
+LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with
+thread safety. It is a singleton that's also accessed from C++. The private constructor will
+not allow this class to be initialized, limiting it to getInstance().
+ */
+class LogOutput private constructor() {
+    val logs = Collections.synchronizedList(mutableStateListOf<String>())
+
+    @Composable
+    fun LogOutputComposable() {
+        val rlogs = remember { logs }
+
+        Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) {
+            Column(
+                modifier =
+                    Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) {
+                    for (log in rlogs) {
+                        Text(log, modifier = Modifier.padding(0.dp))
+                    }
+                }
+        }
+    }
+
+    fun clearText() {
+        logs.clear()
+    }
+
+    fun addLog(log: String) {
+        logs.add(log)
+    }
+
+    companion object {
+        @Volatile private var instance: LogOutput? = null
+
+        @JvmStatic
+        fun getInstance(): LogOutput {
+            if (instance == null) {
+                synchronized(this) {
+                    if (instance == null) {
+                        instance = LogOutput()
+                    }
+                }
+            }
+            return instance!!
+        }
+    }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
index f3f4404..2ccd8d7 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -31,17 +31,19 @@
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
 import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme
+import java.util.*
 
 class MainActivity : ComponentActivity() {
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContent {
             JetpackTheme {
                 Surface(
-                        modifier = Modifier.fillMaxSize(),
-                        color = MaterialTheme.colorScheme.background
-                ) { BufferDemosApp() }
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.background) {
+                        BufferDemosApp()
+                    }
             }
         }
     }
@@ -67,38 +69,32 @@
     val backStackEntry by navController.currentBackStackEntryAsState()
     // Get the name of the current screen
     val currentScreen =
-            BufferDemoScreen.findByRoute(
-                    backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route
-            )
+        BufferDemoScreen.findByRoute(
+            backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
 
     Scaffold(
-            topBar = {
-                BufferDemosAppBar(
-                        currentScreen = currentScreen,
-                        canNavigateBack = navController.previousBackStackEntry != null,
-                        navigateUp = { navController.navigateUp() }
-                )
-            }
-    ) {
-        NavHost(
+        topBar = {
+            BufferDemosAppBar(
+                currentScreen = currentScreen,
+                canNavigateBack = navController.previousBackStackEntry != null,
+                navigateUp = { navController.navigateUp() })
+        }) {
+            NavHost(
                 navController = navController,
                 startDestination = BufferDemoScreen.Start.route,
-                modifier = Modifier.padding(10.dp)
-        ) {
-            composable(route = BufferDemoScreen.Start.route) {
-                DemoList(
-                        onButtonClicked = {
-                            navController.navigate(it)
-                        },
-                )
-            }
-            composable(route = BufferDemoScreen.Demo1.route) {
-                DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
-            }
-            composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
-            composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+                modifier = Modifier.padding(10.dp)) {
+                    composable(route = BufferDemoScreen.Start.route) {
+                        DemoList(
+                            onButtonClicked = { navController.navigate(it) },
+                        )
+                    }
+                    composable(route = BufferDemoScreen.Demo1.route) {
+                        DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
+                    }
+                    composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
+                    composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+                }
         }
-    }
 }
 
 @Composable
@@ -107,25 +103,25 @@
 
     Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
         Column(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalAlignment = Alignment.CenterHorizontally,
-                verticalArrangement = Arrangement.spacedBy(8.dp)
-        ) {
-            Spacer(modifier = Modifier.height(100.dp))
-            Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
-            Spacer(modifier = Modifier.height(8.dp))
-        }
+            modifier = Modifier.fillMaxWidth(),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(8.dp)) {
+                Spacer(modifier = Modifier.height(100.dp))
+                Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
+                Spacer(modifier = Modifier.height(8.dp))
+            }
         Row(modifier = Modifier.weight(2f, false)) {
             Column(
-                    modifier = Modifier.fillMaxWidth(),
-                    horizontalAlignment = Alignment.CenterHorizontally,
-                    verticalArrangement = Arrangement.spacedBy(16.dp)
-            ) {
-                for (item in BufferDemoScreen.values()) {
-                    if (item.route != BufferDemoScreen.Start.route)
-                        SelectDemoButton(name = stringResource(item.title), onClick = { onButtonClicked(item.route) })
+                modifier = Modifier.fillMaxWidth(),
+                horizontalAlignment = Alignment.CenterHorizontally,
+                verticalArrangement = Arrangement.spacedBy(16.dp)) {
+                    for (item in BufferDemoScreen.values()) {
+                        if (item.route != BufferDemoScreen.Start.route)
+                            SelectDemoButton(
+                                name = stringResource(item.title),
+                                onClick = { onButtonClicked(item.route) })
+                    }
                 }
-            }
         }
     }
 }
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
index 3d3fee4..550ad22 100644
--- a/libs/bufferstreams/examples/app/jni/main.cpp
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -13,25 +13,41 @@
 // limitations under the License.
 
 #include <jni.h>
+#include <string>
 
 #include <gui/BufferQueue.h>
 
-extern "C"
-{
-    JNIEXPORT jstring JNICALL
-    Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(
-            JNIEnv* env,
-            jobject /* this */) {
-        const char* hello = "Hello from C++";
-        return env->NewStringUTF(hello);
-    }
+void log(JNIEnv* env, std::string l) {
+    jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput");
+    jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance",
+        "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;");
+    jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V");
+    jobject dmg = env->CallStaticObjectMethod(clazz, getInstance);
 
-    JNIEXPORT void JNICALL
-    Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
-            JNIEnv* /* env */,
-            jobject /* this */) {
-        android::sp<android::IGraphicBufferProducer> producer;
-        android::sp<android::IGraphicBufferConsumer> consumer;
-        android::BufferQueue::createBufferQueue(&producer, &consumer);
-    }
+    jstring jlog = env->NewStringUTF(l.c_str());
+    env->CallVoidMethod(dmg, addLog, jlog);
+}
+
+extern "C" {
+
+JNIEXPORT jstring JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env,
+                                                                             jobject /* this */) {
+    const char* hello = "Hello from C++";
+    return env->NewStringUTF(hello);
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
+        JNIEnv* env, jobject /* thiz */) {
+
+    log(env, "Calling testBufferQueueCreation.");
+    android::sp<android::IGraphicBufferProducer> producer;
+    log(env, "Created producer.");
+    android::sp<android::IGraphicBufferConsumer> consumer;
+    log(env, "Created consumer.");
+    android::BufferQueue::createBufferQueue(&producer, &consumer);
+    log(env, "Created BufferQueue successfully.");
+    log(env, "Done!");
+}
 }
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro
new file mode 100644
index 0000000..7a987fc
--- /dev/null
+++ b/libs/bufferstreams/examples/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; }
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index c84ee1f..44a9e52 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -423,10 +423,10 @@
         "libhidlbase",
         "liblog",
         "libnativewindow",
+        "libselinux",
         "libsync",
         "libui",
         "libutils",
-        "libvndksupport",
     ],
 
     static_libs: [
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index b6a47fb..744201a 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -36,13 +36,45 @@
 #include <gui/TraceUtils.h>
 
 #include <private/gui/BufferQueueThreadState.h>
-#ifndef __ANDROID_VNDK__
+#if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
 #include <binder/PermissionCache.h>
-#include <vndksupport/linker.h>
+#include <selinux/android.h>
+#include <selinux/selinux.h>
 #endif
 
 #include <system/window.h>
 
+namespace {
+#if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
+int selinux_log_suppress_callback(int, const char*, ...) { // NOLINT
+    // DO NOTHING
+    return 0;
+}
+
+bool hasAccessToPermissionService() {
+    char* ctx;
+
+    if (getcon(&ctx) == -1) {
+        // Failed to get current selinux context
+        return false;
+    }
+
+    union selinux_callback cb;
+
+    cb.func_log = selinux_log_suppress_callback;
+    selinux_set_callback(SELINUX_CB_LOG, cb);
+
+    bool hasAccess = selinux_check_access(ctx, "u:object_r:permission_service:s0",
+                                          "service_manager", "find", NULL) == 0;
+    freecon(ctx);
+    cb.func_log = hasAccess ? selinux_log_callback : selinux_vendor_log_callback;
+    selinux_set_callback(SELINUX_CB_LOG, cb);
+
+    return hasAccess;
+}
+#endif
+} // namespace
+
 namespace android {
 
 // Macros for include BufferQueueCore information in log messages
@@ -814,7 +846,7 @@
     // the PermissionController. We need to do a runtime check as well, since
     // the system variant of libgui can be loaded in a vendor process. For eg:
     // if a HAL uses an llndk library that depends on libgui (libmediandk etc).
-    if (!android_is_in_vendor_process()) {
+    if (hasAccessToPermissionService()) {
         const pid_t pid = BufferQueueThreadState::getCallingPid();
         if ((uid != shellUid) &&
             !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index fd8fc8d..38fab9c 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -86,7 +86,7 @@
         defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
         frameRateCategorySmoothSwitchOnly(false),
-        frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
+        frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
         isTrustedOverlay(false),
@@ -930,7 +930,6 @@
     SAFE_PARCEL(output->writeStrongBinder, displayToken);
     SAFE_PARCEL(output->writeUint32, width);
     SAFE_PARCEL(output->writeUint32, height);
-    SAFE_PARCEL(output->writeBool, useIdentityTransform);
     return NO_ERROR;
 }
 
@@ -940,7 +939,6 @@
     SAFE_PARCEL(input->readStrongBinder, &displayToken);
     SAFE_PARCEL(input->readUint32, &width);
     SAFE_PARCEL(input->readUint32, &height);
-    SAFE_PARCEL(input->readBool, &useIdentityTransform);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index 2676e0a..e29ce41 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -76,7 +76,6 @@
     sp<IBinder> displayToken;
     uint32_t width{0};
     uint32_t height{0};
-    bool useIdentityTransform{false};
 
     status_t writeToParcel(Parcel* output) const override;
     status_t readFromParcel(const Parcel* input) override;
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
index bf354e7..1fc80c3 100644
--- a/libs/gui/include/gui/JankInfo.h
+++ b/libs/gui/include/gui/JankInfo.h
@@ -18,7 +18,7 @@
 
 namespace android {
 
-// Jank information tracked by SurfaceFlinger(SF) for perfetto tracing and telemetry.
+// Jank type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
 enum JankType {
     // No Jank
     None = 0x0,
@@ -50,4 +50,16 @@
     Dropped = 0x200,
 };
 
+// Jank severity type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
+enum class JankSeverityType {
+    // Unknown: not enough information to classify the severity of a jank
+    Unknown = 0,
+    // None: no jank
+    None = 1,
+    // Partial: jank caused by missing the deadline by less than the app's frame interval
+    Partial = 2,
+    // Full: jank caused by missing the deadline by more than the app's frame interval
+    Full = 3,
+};
+
 } // namespace android
diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index f7dcbc6..b7aba2b 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -24,9 +24,9 @@
 #include <binder/IBinder.h>
 #include <binder/Parcelable.h>
 
-namespace android {
+#include <gui/IGraphicBufferProducer.h>
 
-class IGraphicBufferProducer;
+namespace android {
 
 namespace view {
 
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 198908d..7c15e7c 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -20,7 +20,6 @@
 #include <android/binder_parcel.h>
 #include <android/native_window.h>
 #include <binder/Parcel.h>
-#include <gui/IGraphicBufferProducer.h>
 #include <gui/Surface.h>
 #include <gui/view/Surface.h>
 #include <system/window.h>
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/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 412931b..c4e3ff6 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -60,9 +60,11 @@
 // --- MotionPredictor ---
 
 MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
-                                 std::function<bool()> checkMotionPredictionEnabled)
+                                 std::function<bool()> checkMotionPredictionEnabled,
+                                 ReportAtomFunction reportAtomFunction)
       : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
-        mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
+        mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
+        mReportAtomFunction(reportAtomFunction) {}
 
 android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
     if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
@@ -90,6 +92,13 @@
         mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
     }
 
+    // Pass input event to the MetricsManager.
+    if (!mMetricsManager) {
+        mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength(),
+                                mReportAtomFunction);
+    }
+    mMetricsManager->onRecord(event);
+
     const int32_t action = event.getActionMasked();
     if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
         ALOGD_IF(isDebug(), "End of event stream");
@@ -135,12 +144,6 @@
     }
     mLastEvent->copyFrom(&event, /*keepHistory=*/false);
 
-    // Pass input event to the MetricsManager.
-    if (!mMetricsManager) {
-        mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength());
-    }
-    mMetricsManager->onRecord(event);
-
     return {};
 }
 
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 67b1032..0412d08 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -46,13 +46,36 @@
 
 } // namespace
 
-MotionPredictorMetricsManager::MotionPredictorMetricsManager(nsecs_t predictionInterval,
-                                                             size_t maxNumPredictions)
+void MotionPredictorMetricsManager::defaultReportAtomFunction(
+        const MotionPredictorMetricsManager::AtomFields& atomFields) {
+    // Call stats_write logging function only on Android targets (not supported on host).
+#ifdef __ANDROID__
+    android::stats::libinput::
+            stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+                            /*stylus_vendor_id=*/0,
+                            /*stylus_product_id=*/0,
+                            atomFields.deltaTimeBucketMilliseconds,
+                            atomFields.alongTrajectoryErrorMeanMillipixels,
+                            atomFields.alongTrajectoryErrorStdMillipixels,
+                            atomFields.offTrajectoryRmseMillipixels,
+                            atomFields.pressureRmseMilliunits,
+                            atomFields.highVelocityAlongTrajectoryRmse,
+                            atomFields.highVelocityOffTrajectoryRmse,
+                            atomFields.scaleInvariantAlongTrajectoryRmse,
+                            atomFields.scaleInvariantOffTrajectoryRmse);
+#endif
+}
+
+MotionPredictorMetricsManager::MotionPredictorMetricsManager(
+        nsecs_t predictionInterval,
+        size_t maxNumPredictions,
+        ReportAtomFunction reportAtomFunction)
       : mPredictionInterval(predictionInterval),
         mMaxNumPredictions(maxNumPredictions),
         mRecentGroundTruthPoints(maxNumPredictions + 1),
         mAggregatedMetrics(maxNumPredictions),
-        mAtomFields(maxNumPredictions) {}
+        mAtomFields(maxNumPredictions),
+        mReportAtomFunction(reportAtomFunction ? reportAtomFunction : defaultReportAtomFunction) {}
 
 void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
     // Convert MotionEvent to GroundTruthPoint.
@@ -81,8 +104,8 @@
             if (mRecentGroundTruthPoints.size() >= 2) {
                 computeAtomFields();
                 reportMetrics();
-                break;
             }
+            break;
         }
     }
 }
@@ -345,28 +368,10 @@
 }
 
 void MotionPredictorMetricsManager::reportMetrics() {
-    // Report one atom for each time bucket.
+    LOG_ALWAYS_FATAL_IF(!mReportAtomFunction);
+    // Report one atom for each prediction time bucket.
     for (size_t i = 0; i < mAtomFields.size(); ++i) {
-        // Call stats_write logging function only on Android targets (not supported on host).
-#ifdef __ANDROID__
-        android::stats::libinput::
-                stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
-                            /*stylus_vendor_id=*/0,
-                            /*stylus_product_id=*/0, mAtomFields[i].deltaTimeBucketMilliseconds,
-                            mAtomFields[i].alongTrajectoryErrorMeanMillipixels,
-                            mAtomFields[i].alongTrajectoryErrorStdMillipixels,
-                            mAtomFields[i].offTrajectoryRmseMillipixels,
-                            mAtomFields[i].pressureRmseMilliunits,
-                            mAtomFields[i].highVelocityAlongTrajectoryRmse,
-                            mAtomFields[i].highVelocityOffTrajectoryRmse,
-                            mAtomFields[i].scaleInvariantAlongTrajectoryRmse,
-                            mAtomFields[i].scaleInvariantOffTrajectoryRmse);
-#endif
-    }
-
-    // Set mock atom fields, if available.
-    if (mMockLoggedAtomFields != nullptr) {
-        *mMockLoggedAtomFields = mAtomFields;
+        mReportAtomFunction(mAtomFields[i]);
     }
 }
 
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index a807d82..54eeb39 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -43,6 +43,13 @@
 }
 
 flag {
+  name: "report_palms_to_gestures_library"
+  namespace: "input"
+  description: "Report touches marked as palm by firmware to gestures library"
+  bug: "302505955"
+}
+
+flag {
   name: "enable_touchpad_typing_palm_rejection"
   namespace: "input"
   description: "Enable additional palm rejection on touchpad while typing"
@@ -69,3 +76,17 @@
   description: "Enable input filter rust implementation"
   bug: "294546335"
 }
+
+flag {
+  name: "override_key_behavior_permission_apis"
+  namespace: "input"
+  description: "enable override key behavior permission APIs"
+  bug: "309018874"
+}
+
+flag {
+  name: "remove_pointer_event_tracking_in_wm"
+  namespace: "input"
+  description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
+  bug: "315321016"
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index b60d7c9..867af79 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -272,6 +272,10 @@
             return false;
         };
 
+        if pointers.len() != pointer_properties.len() {
+            return false;
+        }
+
         for pointer_property in pointer_properties.iter() {
             let pointer_id = pointer_property.id;
             if !pointers.contains(&pointer_id) {
@@ -592,4 +596,43 @@
             )
             .is_ok());
     }
+
+    // Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
+    #[test]
+    fn move_with_wrong_number_of_pointers() {
+        let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        // POINTER 1 DOWN
+        let two_pointer_properties =
+            Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
+                    | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                &two_pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_err());
+    }
 }
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index b420a5a..31cc145 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -39,6 +39,7 @@
 using GroundTruthPoint = MotionPredictorMetricsManager::GroundTruthPoint;
 using PredictionPoint = MotionPredictorMetricsManager::PredictionPoint;
 using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
 
 inline constexpr int NANOS_PER_MILLIS = 1'000'000;
 
@@ -664,9 +665,16 @@
 
 // --- MotionPredictorMetricsManager tests. ---
 
-// Helper function that instantiates a MetricsManager with the given mock logged AtomFields. Takes
-// vectors of ground truth and prediction points of the same length, and passes these points to the
-// MetricsManager. The format of these vectors is expected to be:
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>& reportedAtomFields) {
+    return [&reportedAtomFields](const AtomFields& atomFields) -> void {
+        reportedAtomFields.push_back(atomFields);
+    };
+}
+
+// Helper function that instantiates a MetricsManager that reports metrics to outReportedAtomFields.
+// Takes vectors of ground truth and prediction points of the same length, and passes these points
+// to the MetricsManager. The format of these vectors is expected to be:
 //  • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
 //  • predictionPoints: the first index points to a vector of predictions corresponding to the
 //    source ground truth point with the same index.
@@ -678,15 +686,16 @@
 //       prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
 //       predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
 //
-// The passed-in outAtomFields will contain the logged AtomFields when the function returns.
+// When the function returns, outReportedAtomFields will contain the reported AtomFields.
 //
 // This function returns void so that it can use test assertions.
 void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
                        const std::vector<std::vector<PredictionPoint>>& predictionPoints,
-                       std::vector<AtomFields>& outAtomFields) {
+                       std::vector<AtomFields>& outReportedAtomFields) {
     MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
-                                                 TEST_MAX_NUM_PREDICTIONS);
-    metricsManager.setMockLoggedAtomFields(&outAtomFields);
+                                                 TEST_MAX_NUM_PREDICTIONS,
+                                                 createMockReportAtomFunction(
+                                                         outReportedAtomFields));
 
     // Validate structure of groundTruthPoints and predictionPoints.
     ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
@@ -712,18 +721,18 @@
 //  • Input: no prediction data.
 //  • Expectation: no metrics should be logged.
 TEST(MotionPredictorMetricsManagerTest, NoPredictions) {
-    std::vector<AtomFields> mockLoggedAtomFields;
+    std::vector<AtomFields> reportedAtomFields;
     MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
-                                                 TEST_MAX_NUM_PREDICTIONS);
-    metricsManager.setMockLoggedAtomFields(&mockLoggedAtomFields);
+                                                 TEST_MAX_NUM_PREDICTIONS,
+                                                 createMockReportAtomFunction(reportedAtomFields));
 
     metricsManager.onRecord(makeMotionEvent(
             GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = 0}, .timestamp = 0}));
     metricsManager.onRecord(makeLiftMotionEvent());
 
-    // Check that mockLoggedAtomFields is still empty (as it was initialized empty), ensuring that
+    // Check that reportedAtomFields is still empty (as it was initialized empty), ensuring that
     // no metrics were logged.
-    EXPECT_EQ(0u, mockLoggedAtomFields.size());
+    EXPECT_EQ(0u, reportedAtomFields.size());
 }
 
 // Perfect predictions test:
@@ -744,14 +753,14 @@
         groundTruthPoint.timestamp += TEST_PREDICTION_INTERVAL_NANOS;
     }
 
-    std::vector<AtomFields> atomFields;
-    runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
 
-    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
     // Check that errors are all zero, or NO_DATA_SENTINEL for unreported metrics.
-    for (size_t i = 0; i < atomFields.size(); ++i) {
+    for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
         SCOPED_TRACE(testing::Message() << "i = " << i);
-        const AtomFields& atom = atomFields[i];
+        const AtomFields& atom = reportedAtomFields[i];
         const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
         EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
         // General errors: reported for every time bucket.
@@ -764,7 +773,7 @@
         EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
         EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
         // Scale-invariant errors: reported only for the last time bucket.
-        if (i + 1 == atomFields.size()) {
+        if (i + 1 == reportedAtomFields.size()) {
             EXPECT_EQ(0, atom.scaleInvariantAlongTrajectoryRmse);
             EXPECT_EQ(0, atom.scaleInvariantOffTrajectoryRmse);
         } else {
@@ -801,14 +810,14 @@
             computePressureRmses(groundTruthPoints, predictionPoints);
 
     // Run test.
-    std::vector<AtomFields> atomFields;
-    runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
 
     // Check logged metrics match expectations.
-    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
-    for (size_t i = 0; i < atomFields.size(); ++i) {
+    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+    for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
         SCOPED_TRACE(testing::Message() << "i = " << i);
-        const AtomFields& atom = atomFields[i];
+        const AtomFields& atom = reportedAtomFields[i];
         // Check time bucket delta matches expectation based on index and prediction interval.
         const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
         EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -845,14 +854,14 @@
             computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
 
     // Run test.
-    std::vector<AtomFields> atomFields;
-    runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
 
     // Check logged metrics match expectations.
-    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
-    for (size_t i = 0; i < atomFields.size(); ++i) {
+    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+    for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
         SCOPED_TRACE(testing::Message() << "i = " << i);
-        const AtomFields& atom = atomFields[i];
+        const AtomFields& atom = reportedAtomFields[i];
         // Check time bucket delta matches expectation based on index and prediction interval.
         const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
         EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -896,14 +905,14 @@
             computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
 
     // Run test.
-    std::vector<AtomFields> atomFields;
-    runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
 
     // Check logged metrics match expectations.
-    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
-    for (size_t i = 0; i < atomFields.size(); ++i) {
+    ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+    for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
         SCOPED_TRACE(testing::Message() << "i = " << i);
-        const AtomFields& atom = atomFields[i];
+        const AtomFields& atom = reportedAtomFields[i];
         const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
         EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
 
@@ -926,7 +935,7 @@
         // to general errors (where reported).
         //
         // As above, use absolute value for RMSE, since it must be non-negative.
-        if (i + 2 >= atomFields.size()) {
+        if (i + 2 >= reportedAtomFields.size()) {
             EXPECT_NEAR(static_cast<int>(
                                 1000 * std::abs(generalPositionErrors[i].alongTrajectoryErrorMean)),
                         atom.highVelocityAlongTrajectoryRmse, 1);
@@ -946,7 +955,7 @@
         // to scale-invariant errors by dividing by `strokeVelocty * TEST_MAX_NUM_PREDICTIONS`.
         //
         // As above, use absolute value for RMSE, since it must be non-negative.
-        if (i + 1 == atomFields.size()) {
+        if (i + 1 == reportedAtomFields.size()) {
             const float pathLength = strokeVelocity * TEST_MAX_NUM_PREDICTIONS;
             std::vector<float> alongTrajectoryAbsoluteErrors;
             std::vector<float> offTrajectoryAbsoluteErrors;
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 4ac7ae9..3343114 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -147,4 +147,35 @@
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+// The passed-in pointer must not be nullptr.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>* reportedAtomFields) {
+    return [reportedAtomFields](const AtomFields& atomFields) -> void {
+        reportedAtomFields->push_back(atomFields);
+    };
+}
+
+TEST(MotionPredictorMetricsManagerIntegrationTest, ReportsMetrics) {
+    std::vector<AtomFields> reportedAtomFields;
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; },
+                              createMockReportAtomFunction(&reportedAtomFields));
+
+    ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 1, 0ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 2, 4ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 3, 8ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 4, 4, 12ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 5, 5, 16ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 6, 6, 20ms, /*deviceId=*/0)).ok());
+    ASSERT_TRUE(predictor.record(getMotionEvent(UP, 7, 7, 24ms, /*deviceId=*/0)).ok());
+
+    // The number of atoms reported should equal the number of prediction time buckets, which is
+    // given by the prediction model's output length. For now, this value is always 5, and we
+    // hardcode it because it's not publicly accessible from the MotionPredictor.
+    EXPECT_EQ(5u, reportedAtomFields.size());
+}
+
 } // namespace android
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index b068f48..a98ea86 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1057,7 +1057,12 @@
     /**
      * This surface will vote for the minimum refresh rate.
      */
-    ANATIVEWINDOW_FRAME_RATE_MIN
+    ANATIVEWINDOW_FRAME_RATE_MIN,
+
+    /**
+     * The surface requests a frame rate that is greater than or equal to `frameRate`.
+     */
+    ANATIVEWINDOW_FRAME_RATE_GTE
 };
 
 /*
@@ -1103,17 +1108,34 @@
 enum {
     /**
      * Default value. The layer uses its own frame rate specifications, assuming it has any
-     * specifications, instead of its parent's.
+     * specifications, instead of its parent's. If it does not have its own frame rate
+     * specifications, it will try to use its parent's. It will propagate its specifications to any
+     * descendants that do not have their own.
+     *
+     * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor layer
+     * supersedes this behavior, meaning that this layer will inherit frame rate specifications
+     * regardless of whether it has its own.
      */
-    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 0,
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0,
 
     /**
      * The layer's frame rate specifications will propagate to and override those of its descendant
      * layers.
-     * The layer with this strategy has the ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF
-     * behavior for itself.
+     *
+     * The layer itself has the FRAME_RATE_SELECTION_STRATEGY_PROPAGATE behavior.
+     * Thus, ancestor layer that also has the strategy
+     * FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN will override this layer's
+     * frame rate specifications.
      */
     ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+
+    /**
+     * The layer's frame rate specifications will not propagate to its descendant
+     * layers, even if the descendant layer has no frame rate specifications.
+     * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor
+     * layer supersedes this behavior.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 2,
 };
 
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index cd1ac2b..92fe4c0 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -132,8 +132,8 @@
                              "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
                              "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
                              " colorType %i",
-                             msg, tex.isValid(), dataspace, tex.width(), tex.height(),
-                             tex.hasMipmaps(), tex.isProtected(),
+                             msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
+                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
                              static_cast<int>(tex.textureType()), retrievedTextureInfo,
                              textureInfo.fTarget, textureInfo.fFormat, colorType);
             break;
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/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index b6274ab..03ff58a 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -468,8 +468,8 @@
                                      uint32_t layerCount, uint64_t usage,
                                      bool* outSupported) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    if (auto error = sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
-                                           &descriptorInfo) != OK) {
+    if (sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
+                              &descriptorInfo) != OK) {
         // Usage isn't known to the HAL or otherwise failed validation.
         *outSupported = false;
         return OK;
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 37ebfc4..2ec6d18 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -518,14 +518,16 @@
         }
     }
     {
-        auto value =
-                getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
-                                                                                  bufferHandle);
-        if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) !=
-            value) {
-            ALOGW("Format didn't match, expected %d got %s", format,
-                  value.has_value() ? toString(*value).c_str() : "<null>");
-            return BAD_VALUE;
+        auto expected = static_cast<APixelFormat>(format);
+        if (expected != APixelFormat::IMPLEMENTATION_DEFINED) {
+            auto value =
+                    getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+                                                                                      bufferHandle);
+            if (expected != value) {
+                ALOGW("Format didn't match, expected %d got %s", format,
+                      value.has_value() ? toString(*value).c_str() : "<null>");
+                return BAD_VALUE;
+            }
         }
     }
     {
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/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp
index 9deba01..eda5ea4 100644
--- a/libs/ultrahdr/Android.bp
+++ b/libs/ultrahdr/Android.bp
@@ -21,7 +21,8 @@
 }
 
 cc_library {
-    name: "libultrahdr",
+    name: "libultrahdr-deprecated",
+    enabled: false,
     host_supported: true,
     vendor_available: true,
     export_include_dirs: ["include"],
@@ -46,7 +47,8 @@
 }
 
 cc_library {
-    name: "libjpegencoder",
+    name: "libjpegencoder-deprecated",
+    enabled: false,
     host_supported: true,
     vendor_available: true,
 
@@ -64,7 +66,8 @@
 }
 
 cc_library {
-    name: "libjpegdecoder",
+    name: "libjpegdecoder-deprecated",
+    enabled: false,
     host_supported: true,
     vendor_available: true,
 
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
index e999a8b..2fa361f 100644
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
+++ b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 license {
-    name: "adobe_hdr_gain_map_license",
+    name: "adobe_hdr_gain_map_license-deprecated",
     license_kinds: ["legacy_by_exception_only"],
     license_text: ["NOTICE"],
 }
diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp
index 6c0a2f5..8d9132f 100644
--- a/libs/ultrahdr/fuzzer/Android.bp
+++ b/libs/ultrahdr/fuzzer/Android.bp
@@ -22,7 +22,8 @@
 }
 
 cc_defaults {
-    name: "ultrahdr_fuzzer_defaults",
+    name: "ultrahdr_fuzzer_defaults-deprecated",
+    enabled: false,
     host_supported: true,
     shared_libs: [
         "libimage_io",
@@ -53,7 +54,8 @@
 }
 
 cc_fuzz {
-    name: "ultrahdr_enc_fuzzer",
+    name: "ultrahdr_enc_fuzzer-deprecated",
+    enabled: false,
     defaults: ["ultrahdr_fuzzer_defaults"],
     srcs: [
         "ultrahdr_enc_fuzzer.cpp",
@@ -61,7 +63,8 @@
 }
 
 cc_fuzz {
-    name: "ultrahdr_dec_fuzzer",
+    name: "ultrahdr_dec_fuzzer-deprecated",
+    enabled: false,
     defaults: ["ultrahdr_fuzzer_defaults"],
     srcs: [
         "ultrahdr_dec_fuzzer.cpp",
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
index bda804a..00cc797 100644
--- a/libs/ultrahdr/tests/Android.bp
+++ b/libs/ultrahdr/tests/Android.bp
@@ -22,7 +22,8 @@
 }
 
 cc_test {
-    name: "ultrahdr_unit_test",
+    name: "ultrahdr_unit_test-deprecated",
+    enabled: false,
     test_suites: ["device-tests"],
     srcs: [
         "gainmapmath_test.cpp",
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/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index cefb140..b5cb3cb 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -125,58 +125,84 @@
 
 void InputDeviceMetricsCollector::notifyInputDevicesChanged(
         const NotifyInputDevicesChangedArgs& args) {
-    reportCompletedSessions();
-    onInputDevicesChanged(args.inputDeviceInfos);
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+        onInputDevicesChanged(args.inputDeviceInfos);
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyConfigurationChanged(
         const NotifyConfigurationChangedArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
-    reportCompletedSessions();
-    const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
-        return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
-    };
-    onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
-
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+        const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+            return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
+        };
+        onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
-    reportCompletedSessions();
-    onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime),
-                       [&args](const auto&) { return getUsageSourcesForMotionArgs(args); });
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+        onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime),
+                           [&args](const auto&) { return getUsageSourcesForMotionArgs(args); });
+    }
 
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
 void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
-    reportCompletedSessions();
+    {
+        std::scoped_lock lock(mLock);
+        reportCompletedSessions();
+    }
     mNextListener.notify(args);
 }
 
@@ -185,10 +211,12 @@
     if (isIgnoredInputDeviceId(deviceId)) {
         return;
     }
+    std::scoped_lock lock(mLock);
     mInteractionsQueue.push(DeviceId{deviceId}, timestamp, uids);
 }
 
 void InputDeviceMetricsCollector::dump(std::string& dump) {
+    std::scoped_lock lock(mLock);
     dump += "InputDeviceMetricsCollector:\n";
 
     dump += "  Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
@@ -196,6 +224,10 @@
             dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
 }
 
+void InputDeviceMetricsCollector::monitor() {
+    std::scoped_lock lock(mLock);
+}
+
 void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
     std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
 
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 9633664..1bcd527 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -21,12 +21,14 @@
 #include "NotifyArgs.h"
 #include "SyncQueue.h"
 
+#include <android-base/thread_annotations.h>
 #include <ftl/mixins.h>
 #include <gui/WindowInfo.h>
 #include <input/InputDevice.h>
 #include <chrono>
 #include <functional>
 #include <map>
+#include <mutex>
 #include <set>
 #include <vector>
 
@@ -34,8 +36,6 @@
 
 /**
  * Logs metrics about registered input devices and their usages.
- *
- * All methods in the InputListenerInterface must be called from a single thread.
  */
 class InputDeviceMetricsCollectorInterface : public InputListenerInterface {
 public:
@@ -50,6 +50,9 @@
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
     virtual void dump(std::string& dump) = 0;
+
+    /** Called by the heartbeat to ensure that this component has not deadlocked. */
+    virtual void monitor() = 0;
 };
 
 /** The logging interface for the metrics collector, injected for testing. */
@@ -116,10 +119,12 @@
     void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
                                  const std::set<gui::Uid>& uids) override;
     void dump(std::string& dump) override;
+    void monitor() override;
 
 private:
+    std::mutex mLock;
     InputListenerInterface& mNextListener;
-    InputDeviceMetricsLogger& mLogger;
+    InputDeviceMetricsLogger& mLogger GUARDED_BY(mLock);
     const std::chrono::nanoseconds mUsageSessionTimeout;
 
     // Type-safe wrapper for input device id.
@@ -135,10 +140,10 @@
     using Uid = gui::Uid;
     using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
 
-    std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos GUARDED_BY(mLock);
 
     using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
-    SyncQueue<Interaction> mInteractionsQueue;
+    SyncQueue<Interaction> mInteractionsQueue GUARDED_BY(mLock);
 
     class ActiveSession {
     public:
@@ -166,16 +171,16 @@
     };
 
     // The input devices that currently have active usage sessions.
-    std::map<DeviceId, ActiveSession> mActiveUsageSessions;
+    std::map<DeviceId, ActiveSession> mActiveUsageSessions GUARDED_BY(mLock);
 
-    void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
-    void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info);
+    void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) REQUIRES(mLock);
+    void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info) REQUIRES(mLock);
     using SourceProvider =
             std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
     void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
-                            const SourceProvider& getSources);
-    void onInputDeviceInteraction(const Interaction&);
-    void reportCompletedSessions();
+                            const SourceProvider& getSources) REQUIRES(mLock);
+    void onInputDeviceInteraction(const Interaction&) REQUIRES(mLock);
+    void reportCompletedSessions() REQUIRES(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 02997f0..296f244 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -232,6 +232,9 @@
     mReader->monitor();
     mBlocker->monitor();
     mProcessor->monitor();
+    if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
+        mCollector->monitor();
+    }
     mDispatcher->monitor();
 }
 
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index f1faf69..07a5041 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -445,6 +445,59 @@
     updatePointerControllersLocked();
 }
 
+bool PointerChoreographer::setPointerIcon(
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
+        DeviceId deviceId) {
+    std::scoped_lock _l(mLock);
+    if (deviceId < 0) {
+        LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
+        return false;
+    }
+    const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+    if (!info) {
+        LOG(WARNING) << "No input device info found for id " << deviceId
+                     << ". Cannot set pointer icon.";
+        return false;
+    }
+    const uint32_t sources = info->getSources();
+    const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
+
+    if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
+        stylusPointerIt != mStylusPointersByDevice.end()) {
+        if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+            if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+                LOG(FATAL) << "SpriteIcon should not be null";
+            }
+            stylusPointerIt->second->setCustomPointerIcon(
+                    *std::get<std::unique_ptr<SpriteIcon>>(icon));
+        } else {
+            stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+        }
+    } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+        if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
+            mousePointerIt != mMousePointersByDisplay.end()) {
+            if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+                if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+                    LOG(FATAL) << "SpriteIcon should not be null";
+                }
+                mousePointerIt->second->setCustomPointerIcon(
+                        *std::get<std::unique_ptr<SpriteIcon>>(icon));
+            } else {
+                mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+            }
+        } else {
+            LOG(WARNING) << "No mouse pointer controller found for display " << displayId
+                         << ", device " << deviceId << ".";
+            return false;
+        }
+    } else {
+        LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
+                     << deviceId << ".";
+        return false;
+    }
+    return true;
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         int32_t displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 90a0d3f..9b809a1 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -25,6 +25,8 @@
 
 namespace android {
 
+struct SpriteIcon;
+
 /**
  * A helper class that wraps a factory method that acts as a constructor for the type returned
  * by the factory method.
@@ -59,6 +61,14 @@
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
+     * Set the icon that is shown for the given pointer. The request may fail in some cases, such
+     * as if the device or display was removed, or if the cursor was moved to a different display.
+     * Returns true if the icon was changed successfully, false otherwise.
+     */
+    virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+                                int32_t displayId, DeviceId deviceId) = 0;
+
+    /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
     virtual void dump(std::string& dump) = 0;
@@ -77,6 +87,8 @@
     FloatPoint getMouseCursorPosition(int32_t displayId) override;
     void setShowTouchesEnabled(bool enabled) override;
     void setStylusPointerIconEnabled(bool enabled) override;
+    bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+                        int32_t displayId, DeviceId deviceId) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 7f4de7a..1a94f41 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -649,7 +649,7 @@
         if (newWindows.find(oldWindow) == newWindows.end()) {
             TouchedWindow touchedWindow;
             touchedWindow.windowHandle = oldWindow;
-            touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT;
+            touchedWindow.dispatchMode = InputTarget::DispatchMode::HOVER_EXIT;
             out.push_back(touchedWindow);
         }
     }
@@ -660,7 +660,7 @@
         if (oldWindows.find(newWindow) == oldWindows.end()) {
             // Any windows that have this pointer now, and didn't have it before, should get
             // HOVER_ENTER
-            touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER;
+            touchedWindow.dispatchMode = InputTarget::DispatchMode::HOVER_ENTER;
         } else {
             // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
             if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
@@ -674,7 +674,7 @@
                 }
                 LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
             }
-            touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
+            touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
         touchedWindow.addHoveringPointer(entry.deviceId, pointerId);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
@@ -1090,7 +1090,16 @@
                 }
             }
             if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
-                dropReason = DropReason::STALE;
+                // The event is stale. However, only drop stale events if there isn't an ongoing
+                // gesture. That would allow us to complete the processing of the current stroke.
+                const auto touchStateIt = mTouchStatesByDisplay.find(motionEntry->displayId);
+                if (touchStateIt != mTouchStatesByDisplay.end()) {
+                    const TouchState& touchState = touchStateIt->second;
+                    if (!touchState.hasTouchingPointers(motionEntry->deviceId) &&
+                        !touchState.hasHoveringPointers(motionEntry->deviceId)) {
+                        dropReason = DropReason::STALE;
+                    }
+                }
             }
             if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                 dropReason = DropReason::BLOCKED;
@@ -1316,8 +1325,8 @@
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
             std::bitset<MAX_POINTER_ID + 1> pointerIds;
             pointerIds.set(pointerId);
-            addPointerWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
-                                         pointerIds,
+            addPointerWindowTargetLocked(windowHandle, InputTarget::DispatchMode::OUTSIDE,
+                                         ftl::Flags<InputTarget::Flags>(), pointerIds,
                                          /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
         }
     }
@@ -1595,7 +1604,6 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
             channel->getName();
@@ -1669,7 +1677,6 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 
@@ -1706,7 +1713,6 @@
         }
         InputTarget target;
         target.inputChannel = channel;
-        target.flags = InputTarget::Flags::DISPATCH_AS_IS;
         inputTargets.push_back(target);
     }
     return inputTargets;
@@ -1822,9 +1828,8 @@
     LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
 
     std::vector<InputTarget> inputTargets;
-    addWindowTargetLocked(focusedWindow,
-                          InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
-                          getDownTime(*entry), inputTargets);
+    addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
+                          InputTarget::Flags::FOREGROUND, getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1929,10 +1934,9 @@
                 findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, injectionResult);
         if (injectionResult == InputEventInjectionResult::SUCCEEDED) {
             LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
-            addWindowTargetLocked(focusedWindow,
-                                  InputTarget::Flags::FOREGROUND |
-                                          InputTarget::Flags::DISPATCH_AS_IS,
-                                  getDownTime(*entry), inputTargets);
+            addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
+                                  InputTarget::Flags::FOREGROUND, getDownTime(*entry),
+                                  inputTargets);
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -1979,7 +1983,6 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 }
@@ -2305,6 +2308,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);
@@ -2384,7 +2394,7 @@
             }
 
             // Set target flags.
-            ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
+            ftl::Flags<InputTarget::Flags> targetFlags;
 
             if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
                 // There should only be one touched window that can be "foreground" for the pointer.
@@ -2407,8 +2417,8 @@
                 pointerIds.set(pointerId);
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-                tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId,
-                                                 pointerIds,
+                tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
+                                                 targetFlags, entry.deviceId, pointerIds,
                                                  isDownOrPointerDown
                                                          ? std::make_optional(entry.eventTime)
                                                          : std::nullopt);
@@ -2425,13 +2435,14 @@
                     if (wallpaper != nullptr) {
                         ftl::Flags<InputTarget::Flags> wallpaperFlags =
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
-                                InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED |
-                                InputTarget::Flags::DISPATCH_AS_IS;
+                                InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
                         if (isSplit) {
                             wallpaperFlags |= InputTarget::Flags::SPLIT;
                         }
-                        tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, entry.deviceId,
-                                                         pointerIds, entry.eventTime);
+                        tempTouchState.addOrUpdateWindow(wallpaper,
+                                                         InputTarget::DispatchMode::AS_IS,
+                                                         wallpaperFlags, entry.deviceId, pointerIds,
+                                                         entry.eventTime);
                     }
                 }
             }
@@ -2521,8 +2532,8 @@
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
                 addPointerWindowTargetLocked(oldTouchedWindowHandle,
-                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                                             pointerIds,
+                                             InputTarget::DispatchMode::SLIPPERY_EXIT,
+                                             ftl::Flags<InputTarget::Flags>(), pointerIds,
                                              touchedWindow.getDownTimeInTarget(entry.deviceId),
                                              targets);
 
@@ -2531,8 +2542,7 @@
                     isSplit = !isFromMouse;
                 }
 
-                ftl::Flags<InputTarget::Flags> targetFlags =
-                        InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER;
+                ftl::Flags<InputTarget::Flags> targetFlags;
                 if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) {
                     targetFlags |= InputTarget::Flags::FOREGROUND;
                 }
@@ -2545,8 +2555,10 @@
                     targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
                 }
 
-                tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags,
-                                                 entry.deviceId, pointerIds, entry.eventTime);
+                tempTouchState.addOrUpdateWindow(newTouchedWindowHandle,
+                                                 InputTarget::DispatchMode::SLIPPERY_ENTER,
+                                                 targetFlags, entry.deviceId, pointerIds,
+                                                 entry.eventTime);
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
@@ -2579,7 +2591,8 @@
                 getHoveringWindowsLocked(oldState, tempTouchState, entry);
         for (const TouchedWindow& touchedWindow : hoveringWindows) {
             std::optional<InputTarget> target =
-                    createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+                    createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                            touchedWindow.targetFlags,
                                             touchedWindow.getDownTimeInTarget(entry.deviceId));
             if (!target) {
                 continue;
@@ -2616,7 +2629,7 @@
         if (foregroundWindowHandle) {
             const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (InputTarget& target : targets) {
-                if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
+                if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     sp<WindowInfoHandle> targetWindow =
                             getWindowHandleLocked(target.inputChannel->getConnectionToken());
                     if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
@@ -2642,8 +2655,8 @@
         if (touchingPointers.none()) {
             continue;
         }
-        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                                     touchingPointers,
+        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                     touchedWindow.targetFlags, touchingPointers,
                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
@@ -2668,7 +2681,7 @@
     // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no
     // window that is actually receiving the entire gesture.
     if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) {
-            return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE);
+            return target.dispatchMode == InputTarget::DispatchMode::OUTSIDE;
         })) {
         LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: "
                   << entry.getDescription();
@@ -2678,24 +2691,14 @@
 
     outInjectionResult = InputEventInjectionResult::SUCCEEDED;
 
+    // Now that we have generated all of the input targets for this event, reset the dispatch
+    // mode for all touched window to AS_IS.
     for (TouchedWindow& touchedWindow : tempTouchState.windows) {
-        // Targets that we entered in a slippery way will now become AS-IS targets
-        if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
-            touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
-            touchedWindow.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS;
-        }
+        touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
     }
 
     // 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) {
@@ -2824,7 +2827,7 @@
 
 std::optional<InputTarget> InputDispatcher::createInputTargetLocked(
         const sp<android::gui::WindowInfoHandle>& windowHandle,
-        ftl::Flags<InputTarget::Flags> targetFlags,
+        InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
         std::optional<nsecs_t> firstDownTimeInTarget) const {
     std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
     if (inputChannel == nullptr) {
@@ -2834,6 +2837,7 @@
     InputTarget inputTarget;
     inputTarget.inputChannel = inputChannel;
     inputTarget.windowHandle = windowHandle;
+    inputTarget.dispatchMode = dispatchMode;
     inputTarget.flags = targetFlags;
     inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
     inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
@@ -2848,6 +2852,7 @@
 }
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
+                                            InputTarget::DispatchMode dispatchMode,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
                                             std::vector<InputTarget>& inputTargets) const {
@@ -2862,7 +2867,8 @@
 
     if (it == inputTargets.end()) {
         std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+                                        firstDownTimeInTarget);
         if (!target) {
             return;
         }
@@ -2881,9 +2887,9 @@
 
 void InputDispatcher::addPointerWindowTargetLocked(
         const sp<android::gui::WindowInfoHandle>& windowHandle,
-        ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds,
-        std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const
-        REQUIRES(mLock) {
+        InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+        std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget,
+        std::vector<InputTarget>& inputTargets) const REQUIRES(mLock) {
     if (pointerIds.none()) {
         for (const auto& target : inputTargets) {
             LOG(INFO) << "Target: " << target;
@@ -2904,7 +2910,7 @@
     // input targets for hovering pointers and for touching pointers.
     // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new
     // target instead.
-    if (it != inputTargets.end() && it->flags.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
+    if (it != inputTargets.end() && it->dispatchMode == InputTarget::DispatchMode::HOVER_EXIT) {
         // Force the code below to create a new input target
         it = inputTargets.end();
     }
@@ -2913,7 +2919,8 @@
 
     if (it == inputTargets.end()) {
         std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+                                        firstDownTimeInTarget);
         if (!target) {
             return;
         }
@@ -2921,11 +2928,16 @@
         it = inputTargets.end() - 1;
     }
 
+    if (it->dispatchMode != dispatchMode) {
+        LOG(ERROR) << __func__ << ": DispatchMode doesn't match! ignoring new mode="
+                   << ftl::enum_string(dispatchMode) << ", it=" << *it;
+    }
     if (it->flags != targetFlags) {
-        LOG(ERROR) << "Flags don't match! targetFlags=" << targetFlags.string() << ", it=" << *it;
+        LOG(ERROR) << __func__ << ": Flags don't match! new targetFlags=" << targetFlags.string()
+                   << ", it=" << *it;
     }
     if (it->globalScaleFactor != windowInfo->globalScaleFactor) {
-        LOG(ERROR) << "Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
+        LOG(ERROR) << __func__ << ": Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
                    << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
     }
 
@@ -2940,7 +2952,6 @@
     for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
         InputTarget target;
         target.inputChannel = monitor.inputChannel;
-        target.flags = InputTarget::Flags::DISPATCH_AS_IS;
         // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
         // touch and global monitoring works as intended even without setting firstDownTimeInTarget
         if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
@@ -3267,41 +3278,29 @@
                       connection->getInputChannelName().c_str());
                 logOutboundMotionDetails("  ", *splitMotionEntry);
             }
-            enqueueDispatchEntriesLocked(currentTime, connection, std::move(splitMotionEntry),
-                                         inputTarget);
+            enqueueDispatchEntryAndStartDispatchCycleLocked(currentTime, connection,
+                                                            std::move(splitMotionEntry),
+                                                            inputTarget);
             return;
         }
     }
 
     // Not splitting.  Enqueue dispatch entries for the event as is.
-    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
+    enqueueDispatchEntryAndStartDispatchCycleLocked(currentTime, connection, eventEntry,
+                                                    inputTarget);
 }
 
-void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
-                                                   const std::shared_ptr<Connection>& connection,
-                                                   std::shared_ptr<const EventEntry> eventEntry,
-                                                   const InputTarget& inputTarget) {
+void InputDispatcher::enqueueDispatchEntryAndStartDispatchCycleLocked(
+        nsecs_t currentTime, const std::shared_ptr<Connection>& connection,
+        std::shared_ptr<const EventEntry> eventEntry, const InputTarget& inputTarget) {
     ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, id=0x%" PRIx32 ")",
+                   StringPrintf("enqueueDispatchEntryAndStartDispatchCycleLocked(inputChannel=%s, "
+                                "id=0x%" PRIx32 ")",
                                 connection->getInputChannelName().c_str(), eventEntry->id));
-    LOG_ALWAYS_FATAL_IF(!inputTarget.flags.any(InputTarget::DISPATCH_MASK),
-                        "No dispatch flags are set for %s", eventEntry->getDescription().c_str());
 
     const bool wasEmpty = connection->outboundQueue.empty();
 
-    // Enqueue dispatch entries for the requested modes.
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_HOVER_EXIT);
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_OUTSIDE);
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_HOVER_ENTER);
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_IS);
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT);
-    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
+    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget);
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
     if (wasEmpty && !connection->outboundQueue.empty()) {
@@ -3311,20 +3310,11 @@
 
 void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<const EventEntry> eventEntry,
-                                                 const InputTarget& inputTarget,
-                                                 ftl::Flags<InputTarget::Flags> dispatchMode) {
-    ftl::Flags<InputTarget::Flags> inputTargetFlags = inputTarget.flags;
-    if (!inputTargetFlags.any(dispatchMode)) {
-        return;
-    }
-
-    inputTargetFlags.clear(InputTarget::DISPATCH_MASK);
-    inputTargetFlags |= dispatchMode;
-
+                                                 const InputTarget& inputTarget) {
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
     std::unique_ptr<DispatchEntry> dispatchEntry =
-            createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);
+            createDispatchEntry(inputTarget, eventEntry, inputTarget.flags);
 
     // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
     // different EventEntry than what was passed in.
@@ -3350,15 +3340,15 @@
                 int32_t resolvedAction = motionEntry.action;
                 int32_t resolvedFlags = motionEntry.flags;
 
-                if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
+                if (inputTarget.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
-                } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_EXIT) {
                     resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
-                } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_ENTER)) {
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_ENTER) {
                     resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
-                } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_EXIT) {
                     resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
-                } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_ENTER) {
                     resolvedAction = AMOTION_EVENT_ACTION_DOWN;
                 }
                 if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
@@ -3430,7 +3420,7 @@
                           << cancelEvent->getDescription();
                 std::unique_ptr<DispatchEntry> cancelDispatchEntry =
                         createDispatchEntry(inputTarget, std::move(cancelEvent),
-                                            InputTarget::Flags::DISPATCH_AS_IS);
+                                            ftl::Flags<InputTarget::Flags>());
 
                 // Send these cancel events to the queue before sending the event from the new
                 // device.
@@ -3530,7 +3520,7 @@
     std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens;
     std::vector<std::shared_ptr<Connection>> newConnections;
     for (const InputTarget& target : targets) {
-        if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
+        if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
             continue; // Skip windows that receive ACTION_OUTSIDE
         }
 
@@ -3981,16 +3971,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 +3983,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.",
@@ -4016,8 +3997,7 @@
 
     const bool wasEmpty = connection->outboundQueue.empty();
     // The target to use if we don't find a window associated with the channel.
-    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel,
-                                     .flags = InputTarget::Flags::DISPATCH_AS_IS};
+    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel};
     const auto& token = connection->inputChannel->getConnectionToken();
 
     for (size_t i = 0; i < cancelationEvents.size(); i++) {
@@ -4031,8 +4011,8 @@
                         ? std::make_optional(keyEntry.displayId)
                         : std::nullopt;
                 if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
-                    addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
-                                          keyEntry.downTime, targets);
+                    addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
+                                          /*targetFlags=*/{}, keyEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                 }
@@ -4051,8 +4031,17 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
-                                                 pointerIds, motionEntry.downTime, targets);
+                    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::DispatchMode::AS_IS,
+                                                 ftl::Flags<InputTarget::Flags>(), pointerIds,
+                                                 motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                     const auto it = mDisplayInfos.find(motionEntry.displayId);
@@ -4082,8 +4071,7 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
-        enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0],
-                                   InputTarget::Flags::DISPATCH_AS_IS);
+        enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]);
     }
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
@@ -4126,8 +4114,9 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addPointerWindowTargetLocked(windowHandle, targetFlags, pointerIds,
-                                                 motionEntry.downTime, targets);
+                    addPointerWindowTargetLocked(windowHandle, InputTarget::DispatchMode::AS_IS,
+                                                 targetFlags, pointerIds, motionEntry.downTime,
+                                                 targets);
                 } else {
                     targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
                                                      .flags = targetFlags});
@@ -4156,8 +4145,7 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
-        enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0],
-                                   InputTarget::Flags::DISPATCH_AS_IS);
+        enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]);
     }
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
@@ -5504,12 +5492,12 @@
         // Add new window.
         nsecs_t downTimeInTarget = now();
         ftl::Flags<InputTarget::Flags> newTargetFlags =
-                oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS);
+                oldTargetFlags & (InputTarget::Flags::SPLIT);
         if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) {
             newTargetFlags |= InputTarget::Flags::FOREGROUND;
         }
-        state->addOrUpdateWindow(toWindowHandle, newTargetFlags, deviceId, pointerIds,
-                                 downTimeInTarget);
+        state->addOrUpdateWindow(toWindowHandle, InputTarget::DispatchMode::AS_IS, newTargetFlags,
+                                 deviceId, pointerIds, downTimeInTarget);
 
         // Store the dragging window.
         if (isDragDrop) {
@@ -5888,7 +5876,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 +5889,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 +5919,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 +5930,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 +5969,7 @@
         removeMonitorChannelLocked(connectionToken);
     }
 
-    mLooper->removeFd(connection->inputChannel->getFd());
+    mLooper->removeFd(connection->inputChannel->getFd().get());
 
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
@@ -6212,8 +6200,7 @@
         if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
             const InputTarget target{.inputChannel = connection->inputChannel,
                                      .flags = dispatchEntry->targetFlags};
-            enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target,
-                                       InputTarget::Flags::DISPATCH_AS_IS);
+            enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target);
         }
         releaseDispatchEntry(std::move(dispatchEntry));
     }
@@ -6856,18 +6843,15 @@
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        addPointerWindowTargetLocked(oldWallpaper,
-                                     oldTouchedWindow.targetFlags |
-                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                                     pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId),
-                                     targets);
+        addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
+                                     oldTouchedWindow.targetFlags, pointerIds,
+                                     oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
     }
 
     if (newWallpaper != nullptr) {
-        state.addOrUpdateWindow(newWallpaper,
-                                InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER |
-                                        InputTarget::Flags::WINDOW_IS_OBSCURED |
+        state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
+                                InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
                                 deviceId, pointerIds);
     }
@@ -6903,12 +6887,11 @@
 
     if (newWallpaper != nullptr) {
         nsecs_t downTimeInTarget = now();
-        ftl::Flags<InputTarget::Flags> wallpaperFlags =
-                oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS);
+        ftl::Flags<InputTarget::Flags> wallpaperFlags = oldTargetFlags & InputTarget::Flags::SPLIT;
         wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
                 InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
-        state.addOrUpdateWindow(newWallpaper, wallpaperFlags, deviceId, pointerIds,
-                                downTimeInTarget);
+        state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
+                                deviceId, pointerIds, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
                 getConnectionLocked(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
@@ -6950,4 +6933,21 @@
     mConfig.keyRepeatDelay = delay.count();
 }
 
+bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token, int32_t displayId,
+                                        DeviceId deviceId, int32_t pointerId) {
+    std::scoped_lock _l(mLock);
+    auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+    if (touchStateIt == mTouchStatesByDisplay.end()) {
+        return false;
+    }
+    for (const TouchedWindow& window : touchStateIt->second.windows) {
+        if (window.windowHandle->getToken() == token &&
+            (window.hasTouchingPointer(deviceId, pointerId) ||
+             window.hasHoveringPointer(deviceId, pointerId))) {
+            return true;
+        }
+    }
+    return false;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 8aa4a87..3f99b2d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -148,6 +148,9 @@
     void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
                                    std::chrono::nanoseconds delay) override;
 
+    bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+                           int32_t pointerId) override;
+
 private:
     enum class DropReason {
         NOT_DROPPED,
@@ -528,13 +531,15 @@
 
     std::optional<InputTarget> createInputTargetLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle,
-            ftl::Flags<InputTarget::Flags> targetFlags,
+            InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
             std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                               InputTarget::DispatchMode dispatchMode,
                                ftl::Flags<InputTarget::Flags> targetFlags,
                                std::optional<nsecs_t> firstDownTimeInTarget,
                                std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                      InputTarget::DispatchMode dispatchMode,
                                       ftl::Flags<InputTarget::Flags> targetFlags,
                                       std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                       std::optional<nsecs_t> firstDownTimeInTarget,
@@ -580,14 +585,12 @@
                                     const std::shared_ptr<Connection>& connection,
                                     std::shared_ptr<const EventEntry>,
                                     const InputTarget& inputTarget) REQUIRES(mLock);
-    void enqueueDispatchEntriesLocked(nsecs_t currentTime,
-                                      const std::shared_ptr<Connection>& connection,
-                                      std::shared_ptr<const EventEntry>,
-                                      const InputTarget& inputTarget) REQUIRES(mLock);
+    void enqueueDispatchEntryAndStartDispatchCycleLocked(
+            nsecs_t currentTime, const std::shared_ptr<Connection>& connection,
+            std::shared_ptr<const EventEntry>, const InputTarget& inputTarget) REQUIRES(mLock);
     void enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
                                     std::shared_ptr<const EventEntry>,
-                                    const InputTarget& inputTarget,
-                                    ftl::Flags<InputTarget::Flags> dispatchMode) REQUIRES(mLock);
+                                    const InputTarget& inputTarget) REQUIRES(mLock);
     status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const;
     void startDispatchCycleLocked(nsecs_t currentTime,
                                   const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 343630c..c02c5d6 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -95,6 +95,7 @@
     } else {
         out << "<null>";
     }
+    out << ", dispatchMode=" << ftl::enum_string(target.dispatchMode).c_str();
     out << ", targetFlags=" << target.flags.string();
     out << ", pointers=" << target.getPointerInfoString();
     out << "}";
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 8b8a35a..aef866b 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -51,46 +51,39 @@
          * the same UID from watching all touches. */
         ZERO_COORDS = 1 << 3,
 
-        /* This flag indicates that the event should be sent as is.
-         * Should always be set unless the event is to be transmuted. */
-        DISPATCH_AS_IS = 1 << 8,
-
-        /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
-         * of the area of this target and so should instead be delivered as an
-         * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
-        DISPATCH_AS_OUTSIDE = 1 << 9,
-
-        /* This flag indicates that a hover sequence is starting in the given window.
-         * The event is transmuted into ACTION_HOVER_ENTER. */
-        DISPATCH_AS_HOVER_ENTER = 1 << 10,
-
-        /* This flag indicates that a hover event happened outside of a window which handled
-         * previous hover events, signifying the end of the current hover sequence for that
-         * window.
-         * The event is transmuted into ACTION_HOVER_ENTER. */
-        DISPATCH_AS_HOVER_EXIT = 1 << 11,
-
-        /* This flag indicates that the event should be canceled.
-         * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
-         * outside of a window. */
-        DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
-
-        /* This flag indicates that the event should be dispatched as an initial down.
-         * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
-         * into a new window. */
-        DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
-
         /* This flag indicates that the target of a MotionEvent is partly or wholly
          * obscured by another visible window above it.  The motion event should be
          * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
         WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
     };
 
-    /* Mask for all dispatch modes. */
-    static constexpr const ftl::Flags<InputTarget::Flags> DISPATCH_MASK =
-            ftl::Flags<InputTarget::Flags>() | Flags::DISPATCH_AS_IS | Flags::DISPATCH_AS_OUTSIDE |
-            Flags::DISPATCH_AS_HOVER_ENTER | Flags::DISPATCH_AS_HOVER_EXIT |
-            Flags::DISPATCH_AS_SLIPPERY_EXIT | Flags::DISPATCH_AS_SLIPPERY_ENTER;
+    enum class DispatchMode {
+        /* This flag indicates that the event should be sent as is.
+         * Should always be set unless the event is to be transmuted. */
+        AS_IS,
+        /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+         * of the area of this target and so should instead be delivered as an
+         * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+        OUTSIDE,
+        /* This flag indicates that a hover sequence is starting in the given window.
+         * The event is transmuted into ACTION_HOVER_ENTER. */
+        HOVER_ENTER,
+        /* This flag indicates that a hover event happened outside of a window which handled
+         * previous hover events, signifying the end of the current hover sequence for that
+         * window.
+         * The event is transmuted into ACTION_HOVER_ENTER. */
+        HOVER_EXIT,
+        /* This flag indicates that the event should be canceled.
+         * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
+         * outside of a window. */
+        SLIPPERY_EXIT,
+        /* This flag indicates that the event should be dispatched as an initial down.
+         * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
+         * into a new window. */
+        SLIPPERY_ENTER,
+
+        ftl_last = SLIPPERY_ENTER,
+    };
 
     // The input channel to be targeted.
     std::shared_ptr<InputChannel> inputChannel;
@@ -98,6 +91,9 @@
     // Flags for the input target.
     ftl::Flags<Flags> flags;
 
+    // The dispatch mode that should be used for this target.
+    DispatchMode dispatchMode = DispatchMode::AS_IS;
+
     // Scaling factor to apply to MotionEvent as it is delivered.
     // (ignored for KeyEvents)
     float globalScaleFactor = 1.0f;
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 2ead171..e8d8c18 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -71,6 +71,7 @@
 }
 
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
+                                   InputTarget::DispatchMode dispatchMode,
                                    ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                                    std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
@@ -85,10 +86,8 @@
         // An alternative design choice here would have been to compare here by token, but to
         // store per-pointer transform.
         if (touchedWindow.windowHandle == windowHandle) {
+            touchedWindow.dispatchMode = dispatchMode;
             touchedWindow.targetFlags |= targetFlags;
-            if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
-                touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_IS);
-            }
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
@@ -101,6 +100,7 @@
     }
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
+    touchedWindow.dispatchMode = dispatchMode;
     touchedWindow.targetFlags = targetFlags;
     touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
     if (firstDownTimeInTarget) {
@@ -133,20 +133,6 @@
     }
 }
 
-void TouchState::filterNonAsIsTouchWindows() {
-    for (size_t i = 0; i < windows.size();) {
-        TouchedWindow& window = windows[i];
-        if (window.targetFlags.any(InputTarget::Flags::DISPATCH_AS_IS |
-                                   InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
-            window.targetFlags.clear(InputTarget::DISPATCH_MASK);
-            window.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS;
-            i += 1;
-        } else {
-            windows.erase(windows.begin() + i);
-        }
-    }
-}
-
 void TouchState::cancelPointersForWindowsExcept(DeviceId deviceId,
                                                 std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                                 const sp<IBinder>& token) {
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index e79c73b..e0a84e8 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -44,6 +44,7 @@
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
                                          const sp<android::gui::WindowInfoHandle>& windowHandle);
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                           InputTarget::DispatchMode dispatchMode,
                            ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                            std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
@@ -54,7 +55,6 @@
 
     void removeAllPointersForDevice(DeviceId deviceId);
     void removeWindowByToken(const sp<IBinder>& token);
-    void filterNonAsIsTouchWindows();
 
     // Cancel pointers for current set of windows except the window with particular binder token.
     void cancelPointersForWindowsExcept(DeviceId deviceId,
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..c604353 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -31,6 +31,7 @@
 // Focus tracking for touch.
 struct TouchedWindow {
     sp<gui::WindowInfoHandle> windowHandle;
+    InputTarget::DispatchMode dispatchMode = InputTarget::DispatchMode::AS_IS;
     ftl::Flags<InputTarget::Flags> targetFlags;
 
     // Hovering
@@ -48,15 +49,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/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index bc7b644..001dc6c 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -223,6 +223,12 @@
      */
     virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
                                            std::chrono::nanoseconds delay) = 0;
+
+    /*
+     * Determine if a pointer from a device is being dispatched to the given window.
+     */
+    virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+                                   int32_t pointerId) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index ef74a55..c44486f 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -22,6 +22,8 @@
 
 namespace android {
 
+struct SpriteIcon;
+
 struct FloatPoint {
     float x;
     float y;
@@ -135,6 +137,12 @@
 
     /* Sets the associated display of this pointer. Pointer should show on that display. */
     virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
+
+    /* Sets the pointer icon type for mice or styluses. */
+    virtual void updatePointerIcon(PointerIconStyle iconId) = 0;
+
+    /* Sets the custom pointer icon for mice or styluses. */
+    virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 2dd05f5..5a74a42 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -35,12 +35,10 @@
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
 std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
-    // The evdev multi-touch protocol does not allow userspace applications to query the initial or
-    // current state of the pointers at any time. This means if we clear our accumulated state when
-    // resetting the input mapper, there's no way to rebuild the full initial state of the pointers.
-    // We can only wait for updates to all the pointers and axes. Rather than clearing the state and
-    // rebuilding the state from scratch, we work around this kernel API limitation by never
-    // fully clearing any state specific to the multi-touch protocol.
+    // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS.
+    mMultiTouchMotionAccumulator.reset(getDeviceContext());
+    mPointerIdBits.clear();
+
     return TouchInputMapper::reset(when);
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index b0fc903..d06514a 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -30,23 +30,29 @@
                                             size_t slotCount, bool usingSlotsProtocol) {
     mUsingSlotsProtocol = usingSlotsProtocol;
     mSlots = std::vector<Slot>(slotCount);
+    reset(deviceContext);
+}
 
-    mCurrentSlot = -1;
-    if (mUsingSlotsProtocol) {
-        // Query the driver for the current slot index and use it as the initial slot before we
-        // start reading events from the device.  It is possible that the current slot index will
-        // not be the same as it was when the first event was written into the evdev buffer, which
-        // means the input mapper could start out of sync with the initial state of the events in
-        // the evdev buffer. In the extremely unlikely case that this happens, the data from two
-        // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
-        // touch point to "jump", but at least there will be no stuck touches.
-        int32_t initialSlot;
-        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-            status == OK) {
-            mCurrentSlot = initialSlot;
-        } else {
-            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
-        }
+void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
+    resetSlots();
+
+    if (!mUsingSlotsProtocol) {
+        return;
+    }
+
+    // Query the driver for the current slot index and use it as the initial slot before we
+    // start reading events from the device.  It is possible that the current slot index will
+    // not be the same as it was when the first event was written into the evdev buffer, which
+    // means the input mapper could start out of sync with the initial state of the events in
+    // the evdev buffer. In the extremely unlikely case that this happens, the data from two
+    // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
+    // touch point to "jump", but at least there will be no stuck touches.
+    int32_t initialSlot;
+    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+        status == OK) {
+        mCurrentSlot = initialSlot;
+    } else {
+        ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 0e3e2bb..5b55e3d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -83,6 +83,7 @@
         LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
         return mSlots[index];
     }
+    void reset(const InputDeviceContext& deviceContext);
 
 private:
     int32_t mCurrentSlot;
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index 6780dce..b89b7f3 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -22,10 +22,15 @@
 #include <chrono>
 #include <vector>
 
+#include <com_android_input_flags.h>
 #include <linux/input-event-codes.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
+const bool REPORT_PALMS_TO_GESTURES_LIBRARY = input_flags::report_palms_to_gestures_library();
+
 HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext,
                                                MultiTouchMotionAccumulator& motionAccumulator)
       : mDeviceContext(deviceContext),
@@ -84,7 +89,7 @@
         }
         // Some touchpads continue to report contacts even after they've identified them as palms.
         // We want to exclude these contacts from the HardwareStates.
-        if (slot.getToolType() == ToolType::PALM) {
+        if (!REPORT_PALMS_TO_GESTURES_LIBRARY && slot.getToolType() == ToolType::PALM) {
             numPalms++;
             continue;
         }
@@ -100,6 +105,11 @@
         fingerState.position_x = slot.getX();
         fingerState.position_y = slot.getY();
         fingerState.tracking_id = slot.getTrackingId();
+        if (REPORT_PALMS_TO_GESTURES_LIBRARY) {
+            fingerState.tool_type = slot.getToolType() == ToolType::PALM
+                    ? FingerState::ToolType::kPalm
+                    : FingerState::ToolType::kFinger;
+        }
     }
     schs.state.fingers = schs.fingers.data();
     schs.state.finger_cnt = schs.fingers.size();
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index b55c9cc..6d6b7d8 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -17,6 +17,7 @@
 #include "CursorInputMapper.h"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 
 #include "FakePointerController.h"
@@ -38,6 +39,12 @@
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+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 CursorInputMapper.
@@ -60,6 +67,11 @@
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
 
+        mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                        /*isActive=*/true, "local:0", NO_PORT,
+                                        ViewportType::INTERNAL);
+
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 
@@ -139,6 +151,7 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(ACTION_MOVE),
                               WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+                              WithRelativeMotion(10.0f, 20.0f),
                               WithCursorPosition(INVALID_CURSOR_POSITION,
                                                  INVALID_CURSOR_POSITION)))));
 
@@ -178,12 +191,17 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(ACTION_MOVE),
-                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                              WithCoords(30.0f, 40.0f)))));
+                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(30.0f, 40.0f),
+                              WithRelativeMotion(30.0f, 40.0f)))));
 
     // Disable pointer capture. Afterwards, events should be generated the usual way.
     setPointerCapture(false);
-
+    const auto expectedCoords = input_flags::enable_pointer_choreographer()
+            ? WithCoords(0, 0)
+            : WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+    const auto expectedCursorPosition = input_flags::enable_pointer_choreographer()
+            ? WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION)
+            : WithCursorPosition(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
     args.clear();
     args += process(EV_REL, REL_X, 10);
     args += process(EV_REL, REL_Y, 20);
@@ -191,9 +209,8 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
-                              WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f),
-                              WithCursorPosition(INITIAL_CURSOR_X + 10.0f,
-                                                 INITIAL_CURSOR_Y + 20.0f)))));
+                              expectedCoords, expectedCursorPosition,
+                              WithRelativeMotion(10.0f, 20.0f)))));
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 8043812..80319f2 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -54,6 +54,16 @@
               viewport.logicalBottom - 1);
 }
 
+void FakePointerController::updatePointerIcon(PointerIconStyle iconId) {
+    ASSERT_FALSE(mIconStyle.has_value()) << "Pointer icon was set more than once";
+    mIconStyle = iconId;
+}
+
+void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+    ASSERT_FALSE(mCustomIconStyle.has_value()) << "Custom pointer icon was set more than once";
+    mCustomIconStyle = icon.style;
+}
+
 void FakePointerController::assertViewportSet(int32_t displayId) {
     ASSERT_TRUE(mDisplayId);
     ASSERT_EQ(displayId, mDisplayId);
@@ -75,6 +85,26 @@
     ASSERT_EQ(static_cast<size_t>(count), it->second.size());
 }
 
+void FakePointerController::assertPointerIconSet(PointerIconStyle iconId) {
+    ASSERT_TRUE(mIconStyle) << "Pointer icon style was not set";
+    ASSERT_EQ(iconId, mIconStyle);
+    mIconStyle.reset();
+}
+
+void FakePointerController::assertPointerIconNotSet() {
+    ASSERT_EQ(std::nullopt, mIconStyle);
+}
+
+void FakePointerController::assertCustomPointerIconSet(PointerIconStyle iconId) {
+    ASSERT_TRUE(mCustomIconStyle) << "Custom pointer icon was not set";
+    ASSERT_EQ(iconId, mCustomIconStyle);
+    mCustomIconStyle.reset();
+}
+
+void FakePointerController::assertCustomPointerIconNotSet() {
+    ASSERT_EQ(std::nullopt, mCustomIconStyle);
+}
+
 bool FakePointerController::isPointerShown() {
     return mIsPointerShown;
 }
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 9be6a6c..7668011 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -24,6 +24,10 @@
 
 namespace android {
 
+struct SpriteIcon {
+    PointerIconStyle style;
+};
+
 class FakePointerController : public PointerControllerInterface {
 public:
     virtual ~FakePointerController() {}
@@ -35,11 +39,17 @@
     FloatPoint getPosition() const override;
     int32_t getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
+    void updatePointerIcon(PointerIconStyle iconId) override;
+    void setCustomPointerIcon(const SpriteIcon& icon) override;
 
     void assertViewportSet(int32_t displayId);
     void assertViewportNotSet();
     void assertPosition(float x, float y);
     void assertSpotCount(int32_t displayId, int32_t count);
+    void assertPointerIconSet(PointerIconStyle iconId);
+    void assertPointerIconNotSet();
+    void assertCustomPointerIconSet(PointerIconStyle iconId);
+    void assertCustomPointerIconNotSet();
     bool isPointerShown();
 
 private:
@@ -58,6 +68,8 @@
     float mX{0}, mY{0};
     std::optional<int32_t> mDisplayId;
     bool mIsPointerShown{false};
+    std::optional<PointerIconStyle> mIconStyle;
+    std::optional<PointerIconStyle> mCustomIconStyle;
 
     std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
 };
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index 5bea2ba..ff9bd9e 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -18,6 +18,8 @@
 #include <memory>
 
 #include <EventHub.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
 #include <utils/StrongPointer.h>
@@ -31,6 +33,13 @@
 
 namespace android {
 
+namespace {
+
+const auto REPORT_PALMS =
+        ACONFIG_FLAG(com::android::input::flags, report_palms_to_gestures_library);
+
+} // namespace
+
 class HardwareStateConverterTest : public testing::Test {
 public:
     HardwareStateConverterTest()
@@ -192,7 +201,8 @@
     EXPECT_EQ(0u, finger2.flags);
 }
 
-TEST_F(HardwareStateConverterTest, OnePalm) {
+TEST_F_WITH_FLAGS(HardwareStateConverterTest, OnePalmDisableReportPalms,
+                  REQUIRES_FLAGS_DISABLED(REPORT_PALMS)) {
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
@@ -207,7 +217,25 @@
     EXPECT_EQ(0, schs->state.finger_cnt);
 }
 
-TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) {
+TEST_F_WITH_FLAGS(HardwareStateConverterTest, OnePalmEnableReportPalms,
+                  REQUIRES_FLAGS_ENABLED(REPORT_PALMS)) {
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
+    EXPECT_EQ(1, schs->state.finger_cnt);
+    EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+}
+
+TEST_F_WITH_FLAGS(HardwareStateConverterTest, OneFingerTurningIntoAPalmDisableReportPalms,
+                  REQUIRES_FLAGS_DISABLED(REPORT_PALMS)) {
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
     processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
@@ -252,6 +280,56 @@
     EXPECT_NEAR(95, newFinger.position_y, EPSILON);
 }
 
+TEST_F_WITH_FLAGS(HardwareStateConverterTest, OneFingerTurningIntoAPalmEnableReportPalms,
+                  REQUIRES_FLAGS_ENABLED(REPORT_PALMS)) {
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
+    processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
+    EXPECT_EQ(1, schs->state.finger_cnt);
+    EXPECT_EQ(FingerState::ToolType::kFinger, schs->state.fingers[0].tool_type);
+
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 51);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 99);
+
+    schs = processSync(ARBITRARY_TIME);
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
+    ASSERT_EQ(1, schs->state.finger_cnt);
+    EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 97);
+
+    schs = processSync(ARBITRARY_TIME);
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
+    EXPECT_EQ(1, schs->state.finger_cnt);
+    EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 55);
+    processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95);
+    schs = processSync(ARBITRARY_TIME);
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(1, schs->state.touch_cnt);
+    ASSERT_EQ(1, schs->state.finger_cnt);
+    const FingerState& newFinger = schs->state.fingers[0];
+    EXPECT_EQ(FingerState::ToolType::kFinger, newFinger.tool_type);
+    EXPECT_EQ(123, newFinger.tracking_id);
+    EXPECT_NEAR(55, newFinger.position_x, EPSILON);
+    EXPECT_NEAR(95, newFinger.position_y, EPSILON);
+}
+
 TEST_F(HardwareStateConverterTest, ButtonPressed) {
     processAxis(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
     std::optional<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e341782..5002391 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1052,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 = "
@@ -1784,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
@@ -1793,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
@@ -3411,6 +3413,44 @@
 }
 
 /**
+ * A stale stylus HOVER_EXIT event is injected. Since it's a stale event, it should generally be
+ * rejected. But since we already have an ongoing gesture, this event should be processed.
+ * This prevents inconsistent events being handled inside the dispatcher.
+ */
+TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Start hovering with stylus
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    NotifyMotionArgs hoverExit = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                         .build();
+    // Make this 'hoverExit' event stale
+    mFakePolicy->setStaleEventTimeout(100ms);
+    std::this_thread::sleep_for(100ms);
+
+    // It shouldn't be dropped by the dispatcher, even though it's stale.
+    mDispatcher->notifyMotion(hoverExit);
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // Stylus starts hovering again! There should be no crash.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(51))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
+/**
  * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream.
  * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse
  * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
@@ -4318,6 +4358,12 @@
     // Therefore, we should offset them by (100, 100) relative to the screen's top left corner.
     outsideWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_OUTSIDE), WithCoords(-50, -50)));
+
+    // Ensure outsideWindow doesn't get any more events for the gesture.
+    mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT, {PointF{51, 51}}));
+    window->consumeMotionMove();
+    outsideWindow->assertNoEvents();
 }
 
 /**
@@ -7740,12 +7786,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() {
@@ -8182,11 +8231,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.
@@ -8195,12 +8243,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();
 }
 
@@ -9656,6 +9704,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);
 
@@ -9993,6 +10085,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) {
@@ -10014,6 +10119,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
@@ -10891,6 +10997,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() {
@@ -11167,4 +11292,243 @@
     randosWindow->assertNoEvents();
 }
 
+using InputDispatcherPointerInWindowTest = InputDispatcherTest;
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) {
+    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});
+
+    // Hover into the left window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(50))
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+
+    // Hover move to the right window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Stop hovering.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+                    .build());
+
+    right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) {
+    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});
+
+    // First pointer down on left window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .build());
+
+    left->consumeMotionDown();
+    spy->consumeMotionDown();
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+
+    // Second pointer down on right window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+                    .build());
+
+    left->consumeMotionMove();
+    right->consumeMotionDown();
+    spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/1));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/1));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/1));
+
+    // Second pointer up.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+                    .build());
+
+    left->consumeMotionMove();
+    right->consumeMotionUp();
+    spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/1));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/1));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/1));
+
+    // First pointer up.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+                    .build());
+
+    left->consumeMotionUp();
+    spy->consumeMotionUp();
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+    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));
+
+    mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                                /*pointerId=*/0));
+
+    // Hover move into the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+                    .rawXCursorPosition(50)
+                    .rawYCursorPosition(50)
+                    .deviceId(DEVICE_ID)
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse with another device. This cancels the hovering pointer from the first device.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+                    .rawXCursorPosition(51)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+    // a HOVER_EXIT from the first device.
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+                                               SECOND_DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse outside the window. Document the current behavior, where the window does not
+    // receive HOVER_EXIT even though the mouse left the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+                    .rawXCursorPosition(150)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+                                                SECOND_DEVICE_ID,
+                                                /*pointerId=*/0));
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e8b779a..e0a3e94 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -232,6 +232,11 @@
         mResetWasCalled = false;
     }
 
+    void assertResetWasNotCalled() {
+        std::scoped_lock lock(mLock);
+        ASSERT_FALSE(mResetWasCalled) << "Expected reset to not have been called.";
+    }
+
     void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) {
         std::unique_lock<std::mutex> lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
@@ -248,6 +253,11 @@
         mProcessWasCalled = false;
     }
 
+    void assertProcessWasNotCalled() {
+        std::scoped_lock lock(mLock);
+        ASSERT_FALSE(mProcessWasCalled) << "Expected process to not have been called.";
+    }
+
     void setKeyCodeState(int32_t keyCode, int32_t state) {
         mKeyCodeStates.replaceValueFor(keyCode, state);
     }
@@ -2872,6 +2882,60 @@
     ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
 }
 
+TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) {
+    mFakePolicy->clearViewports();
+    FakeInputMapper& mapper =
+            mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                                AINPUT_SOURCE_KEYBOARD);
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    mapper.assertConfigureWasCalled();
+    mapper.assertResetWasNotCalled();
+
+    RawEvent event{.deviceId = EVENTHUB_ID,
+                   .readTime = ARBITRARY_TIME,
+                   .when = ARBITRARY_TIME,
+                   .type = EV_SYN,
+                   .code = SYN_REPORT,
+                   .value = 0};
+
+    // Events are processed normally.
+    unused = mDevice->process(&event, /*count=*/1);
+    mapper.assertProcessWasCalled();
+
+    // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event.
+    // This should reset the mapper.
+    event.type = EV_SYN;
+    event.code = SYN_DROPPED;
+    event.value = 0;
+    unused = mDevice->process(&event, /*count=*/1);
+    mapper.assertProcessWasNotCalled();
+    mapper.assertResetWasCalled();
+
+    // All events until the next SYN_REPORT should be dropped.
+    event.type = EV_KEY;
+    event.code = KEY_A;
+    event.value = 1;
+    unused = mDevice->process(&event, /*count=*/1);
+    mapper.assertProcessWasNotCalled();
+
+    // We get the SYN_REPORT event now, which is not forwarded to mappers.
+    event.type = EV_SYN;
+    event.code = SYN_REPORT;
+    event.value = 0;
+    unused = mDevice->process(&event, /*count=*/1);
+    mapper.assertProcessWasNotCalled();
+
+    // The mapper receives events normally now.
+    event.type = EV_KEY;
+    event.code = KEY_B;
+    event.value = 1;
+    unused = mDevice->process(&event, /*count=*/1);
+    mapper.assertProcessWasCalled();
+}
+
 // --- SwitchInputMapperTest ---
 
 class SwitchInputMapperTest : public InputMapperTest {
@@ -10907,7 +10971,7 @@
     ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
 }
 
-TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
+TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -10930,25 +10994,36 @@
     ASSERT_NO_FATAL_FAILURE(
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
 
-    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
-    // preserved. Resetting should cancel the ongoing gesture.
+    // Reset the mapper. When the mapper is reset, the touch state is also cleared.
     resetMapper(mapper, ARBITRARY_TIME);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
 
-    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
-    // the existing touch state to generate a down event.
+    // Move the second slot pointer, and ensure there are no events, because the touch state was
+    // cleared and no slots should be in use.
     processPosition(mapper, 301, 302);
     processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Release both fingers.
+    processId(mapper, INVALID_TRACKING_ID);
+    processSlot(mapper, FIRST_SLOT);
+    processId(mapper, INVALID_TRACKING_ID);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Start a new gesture, and ensure we get a DOWN event for it.
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 200, 300);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f))));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f))));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
+TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
@@ -11076,6 +11151,66 @@
     ASSERT_FALSE(fakePointerController->isPointerShown());
 }
 
+TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareAxes(POSITION | ID | SLOT | PRESSURE);
+    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+    // First finger down.
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // Assume the kernel buffer overflows, and we get a SYN_DROPPED event.
+    // This will reset the mapper, and thus also reset the touch state.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0);
+    resetMapper(mapper, ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+    // Since the touch state was reset, it doesn't know which slots are active, so any movements
+    // are ignored.
+    processPosition(mapper, 101, 201);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Second finger goes down. This is the first active finger, so we get a DOWN event.
+    processSlot(mapper, SECOND_SLOT);
+    processId(mapper, SECOND_TRACKING_ID);
+    processPosition(mapper, 400, 500);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // First slot is still ignored, only the second one is active.
+    processSlot(mapper, FIRST_SLOT);
+    processPosition(mapper, 102, 202);
+    processSlot(mapper, SECOND_SLOT);
+    processPosition(mapper, 401, 501);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    // Both slots up, so we get the UP event for the active pointer.
+    processSlot(mapper, FIRST_SLOT);
+    processId(mapper, INVALID_TRACKING_ID);
+    processSlot(mapper, SECOND_SLOT);
+    processId(mapper, INVALID_TRACKING_ID);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 1efb797..2457f7c 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -1511,4 +1511,278 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouse) {
+    // Make sure there is a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    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(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertPointerIconNotSet();
+
+    // Set pointer icon for the device.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetMousePointerIconForWrongDisplayId) {
+    // Make sure there is a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    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(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertPointerIconNotSet();
+
+    // Set pointer icon for wrong display id. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+                                               SECOND_DEVICE_ID));
+    pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForWrongDeviceId) {
+    // Make sure there is a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    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(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertPointerIconNotSet();
+
+    // Set pointer icon for wrong device id. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+                                               SECOND_DEVICE_ID));
+    pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForDeviceWithoutPointerController) {
+    // Add two devices, one with a PointerController and the other without PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertPointerIconNotSet();
+
+    // Set pointer icon for the device without PointerController. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+                                               SECOND_DEVICE_ID));
+    pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForMouse) {
+    // Make sure there is a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    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(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertCustomPointerIconNotSet();
+
+    // Set custom pointer icon for the device.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+                                                      PointerIconStyle::TYPE_CUSTOM),
+                                              DISPLAY_ID, DEVICE_ID));
+    pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+    // Set custom pointer icon for wrong device id. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+                                                       PointerIconStyle::TYPE_CUSTOM),
+                                               DISPLAY_ID, SECOND_DEVICE_ID));
+    pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) {
+    // Make sure there are two PointerControllers on different displays.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId());
+
+    // Set pointer icon for one mouse.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    secondMousePc->assertPointerIconNotSet();
+
+    // Set pointer icon for another mouse.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+                                              SECOND_DEVICE_ID));
+    secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    firstMousePc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
+    // Make sure there is a PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    pc->assertPointerIconNotSet();
+
+    // Set pointer icon for the device.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+
+    // Set pointer icon for wrong device id. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+                                               SECOND_DEVICE_ID));
+    pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+    // Make sure there is a PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    pc->assertCustomPointerIconNotSet();
+
+    // Set custom pointer icon for the device.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+                                                      PointerIconStyle::TYPE_CUSTOM),
+                                              DISPLAY_ID, DEVICE_ID));
+    pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+    // Set custom pointer icon for wrong device id. This should be ignored.
+    ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+                                                       PointerIconStyle::TYPE_CUSTOM),
+                                               DISPLAY_ID, SECOND_DEVICE_ID));
+    pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+    // Make sure there are two StylusPointerControllers. They can be on a same display.
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Set pointer icon for one stylus.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    firstStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    secondStylusPc->assertPointerIconNotSet();
+
+    // Set pointer icon for another stylus.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+                                              SECOND_DEVICE_ID));
+    secondStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    firstStylusPc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+    // Make sure there are PointerControllers for a mouse and a stylus.
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Set pointer icon for the mouse.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    mousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    stylusPc->assertPointerIconNotSet();
+
+    // Set pointer icon for the stylus.
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+                                              SECOND_DEVICE_ID));
+    stylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    mousePc->assertPointerIconNotSet();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index 214649c..0bd8fb3 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -41,14 +41,16 @@
 class NotifyStreamProvider {
 public:
     NotifyStreamProvider(FuzzedDataProvider& fdp)
-          : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER), mVerifier("Fuzz verifier") {}
+          : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER) {}
 
     std::optional<NotifyMotionArgs> nextMotion() {
         NotifyMotionArgs args = generateFuzzedMotionArgs(mIdGenerator, mFdp, MAX_RANDOM_DISPLAYS);
+        auto [it, _] = mVerifiers.emplace(args.displayId, "Fuzz Verifier");
+        InputVerifier& verifier = it->second;
         const Result<void> result =
-                mVerifier.processMovement(args.deviceId, args.source, args.action,
-                                          args.getPointerCount(), args.pointerProperties.data(),
-                                          args.pointerCoords.data(), args.flags);
+                verifier.processMovement(args.deviceId, args.source, args.action,
+                                         args.getPointerCount(), args.pointerProperties.data(),
+                                         args.pointerCoords.data(), args.flags);
         if (result.ok()) {
             return args;
         }
@@ -60,7 +62,7 @@
 
     IdGenerator mIdGenerator;
 
-    InputVerifier mVerifier;
+    std::map<int32_t /*displayId*/, InputVerifier> mVerifiers;
 };
 
 } // namespace
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 2f84497..81c570d 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -275,6 +275,8 @@
     void clearSpots() override {}
     int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
     void setDisplayViewport(const DisplayViewport& displayViewport) override {}
+    void updatePointerIcon(PointerIconStyle iconId) override {}
+    void setCustomPointerIcon(const SpriteIcon& icon) override {}
     std::string dump() override { return ""; }
 };
 
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 8b16890..1f72e8b 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -19,6 +19,7 @@
         "PowerHalWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
+        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
new file mode 100644
index 0000000..ef723c2
--- /dev/null
+++ b/services/powermanager/WorkDuration.cpp
@@ -0,0 +1,51 @@
+/**
+ * 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
new file mode 100644
index 0000000..99b5b8b
--- /dev/null
+++ b/services/powermanager/include/android/WorkDuration.h
@@ -0,0 +1,71 @@
+/**
+ * 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..ae2f2db 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -37,6 +37,7 @@
         "libSurfaceFlingerProp",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
     static_libs: [
         "liblayers_proto",
@@ -60,13 +61,8 @@
     ],
 }
 
-cc_library {
-    name: "libcompositionengine",
-    defaults: ["libcompositionengine_defaults"],
-    static_libs: [
-        "libsurfaceflinger_common",
-        "libsurfaceflingerflags",
-    ],
+filegroup {
+    name: "libcompositionengine_sources",
     srcs: [
         "src/planner/CachedSet.cpp",
         "src/planner/Flattener.cpp",
@@ -89,8 +85,23 @@
         "src/OutputLayerCompositionState.cpp",
         "src/RenderSurface.cpp",
     ],
+}
+
+cc_library {
+    name: "libcompositionengine",
+    defaults: ["libcompositionengine_defaults"],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+    ],
+    srcs: [
+        ":libcompositionengine_sources",
+    ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
 }
 
 cc_library {
@@ -114,6 +125,9 @@
         "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
 }
@@ -121,8 +135,13 @@
 cc_test {
     name: "libcompositionengine_test",
     test_suites: ["device-tests"],
+    include_dirs: [
+        "frameworks/native/services/surfaceflinger/common/include",
+        "frameworks/native/services/surfaceflinger/tests/unittests",
+    ],
     defaults: ["libcompositionengine_defaults"],
     srcs: [
+        ":libcompositionengine_sources",
         "tests/planner/CachedSetTest.cpp",
         "tests/planner/FlattenerTest.cpp",
         "tests/planner/LayerStateTest.cpp",
@@ -141,7 +160,6 @@
         "tests/RenderSurfaceTest.cpp",
     ],
     static_libs: [
-        "libcompositionengine",
         "libcompositionengine_mocks",
         "libgui_mocks",
         "librenderengine_mocks",
@@ -150,10 +168,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/DisplayCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
index 98c4af4..6e60839 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
@@ -42,6 +42,10 @@
     // True if this display should be considered secure
     bool isSecure = false;
 
+    // True if this display should be considered protected, as in this display should render DRM
+    // content.
+    bool isProtected = false;
+
     // Optional pointer to the power advisor interface, if one is needed for
     // this display.
     Hwc2::PowerAdvisor* powerAdvisor = nullptr;
@@ -73,6 +77,11 @@
         return *this;
     }
 
+    DisplayCreationArgsBuilder& setIsProtected(bool isProtected) {
+        mArgs.isProtected = isProtected;
+        return *this;
+    }
+
     DisplayCreationArgsBuilder& setPowerAdvisor(Hwc2::PowerAdvisor* powerAdvisor) {
         mArgs.powerAdvisor = powerAdvisor;
         return *this;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
index ca86f4c..643b458 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
@@ -60,7 +60,7 @@
     //
     // advanceFrame must be followed by a call to  onFrameCommitted before
     // advanceFrame may be called again.
-    virtual status_t advanceFrame() = 0;
+    virtual status_t advanceFrame(float hdrSdrRatio) = 0;
 
     // onFrameCommitted is called after the frame has been committed to the
     // hardware composer. The surface collects the release fence for this
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index ccff1ec..a1d6132 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -97,7 +97,7 @@
         const bool isSecure;
 
         // If set to true, the target buffer has protected content support.
-        const bool supportsProtectedContent;
+        const bool isProtected;
 
         // Viewport of the target being rendered to. This is used to determine
         // the shadow light position.
@@ -167,8 +167,7 @@
 static inline bool operator==(const LayerFE::ClientCompositionTargetSettings& lhs,
                               const LayerFE::ClientCompositionTargetSettings& rhs) {
     return lhs.clip.hasSameRects(rhs.clip) && lhs.needsFiltering == rhs.needsFiltering &&
-            lhs.isSecure == rhs.isSecure &&
-            lhs.supportsProtectedContent == rhs.supportsProtectedContent &&
+            lhs.isSecure == rhs.isSecure && lhs.isProtected == rhs.isProtected &&
             lhs.viewport == rhs.viewport && lhs.dataspace == rhs.dataspace &&
             lhs.realContentIsVisible == rhs.realContentIsVisible &&
             lhs.clearContent == rhs.clearContent;
@@ -189,7 +188,7 @@
     PrintTo(settings.clip, os);
     *os << "\n    .needsFiltering = " << settings.needsFiltering;
     *os << "\n    .isSecure = " << settings.isSecure;
-    *os << "\n    .supportsProtectedContent = " << settings.supportsProtectedContent;
+    *os << "\n    .isProtected = " << settings.isProtected;
     *os << "\n    .viewport = ";
     PrintTo(settings.viewport, os);
     *os << "\n    .dataspace = ";
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/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
index 5854674..02cea0d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
@@ -86,7 +86,7 @@
 
     // Queues the drawn buffer for consumption by HWC. readyFence is the fence
     // which will fire when the buffer is ready for consumption.
-    virtual void queueBuffer(base::unique_fd readyFence) = 0;
+    virtual void queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) = 0;
 
     // Called after the HWC calls are made to present the display
     virtual void onPresentDisplayCompleted() = 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..911d67b 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;
@@ -134,7 +137,8 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentFrame() override;
-    virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
+    virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings(
+            const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, ui::Dataspace outputDataspace,
             std::vector<LayerFE*>& outLayerFEs) override;
@@ -164,6 +168,8 @@
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
     compositionengine::Output::ColorProfile pickColorProfile(
             const compositionengine::CompositionRefreshArgs&) const;
+    void updateHwcAsyncWorker();
+    float getHdrSdrRatio(const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
 
     std::string mName;
     std::string mNamePlusId;
@@ -177,6 +183,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/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 692ed24..6b1c318 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -53,6 +53,9 @@
     // If false, this output is not considered secure
     bool isSecure{false};
 
+    // If false, this output is not considered protected
+    bool isProtected{false};
+
     // If true, the current frame on this output uses client composition
     bool usesClientComposition{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/impl/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
index 1c14a43..202145e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
@@ -60,7 +60,7 @@
     void prepareFrame(bool usesClientComposition, bool usesDeviceComposition) override;
     std::shared_ptr<renderengine::ExternalTexture> dequeueBuffer(
             base::unique_fd* bufferFence) override;
-    void queueBuffer(base::unique_fd readyFence) override;
+    void queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) override;
     void onPresentDisplayCompleted() override;
     bool supportsCompositionStrategyPrediction() const override;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index ce2b96f..1f241b0 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -227,6 +227,7 @@
     // Returns the bit-set of differing fields between this LayerState and another LayerState.
     // This bit-set is based on NonUniqueFields only, and excludes GraphicBuffers.
     ftl::Flags<LayerStateField> getDifferingFields(const LayerState& other) const;
+    bool isSourceCropSizeEqual(const LayerState& other) const;
 
     compositionengine::OutputLayer* getOutputLayer() const { return mOutputLayer; }
     int32_t getId() const { return mId.get(); }
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
index 168e433..08d8ff7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
@@ -30,7 +30,7 @@
 
     MOCK_METHOD1(beginFrame, status_t(bool mustRecompose));
     MOCK_METHOD1(prepareFrame, status_t(CompositionType compositionType));
-    MOCK_METHOD0(advanceFrame, status_t());
+    MOCK_METHOD((status_t), advanceFrame, (float), (override));
     MOCK_METHOD0(onFrameCommitted, void());
     MOCK_CONST_METHOD1(dumpAsString, void(String8& result));
     MOCK_METHOD1(resizeBuffers, void(const ui::Size&));
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/include/compositionengine/mock/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
index af8d4bc..c35fd3f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
@@ -40,7 +40,7 @@
     MOCK_METHOD1(beginFrame, status_t(bool mustRecompose));
     MOCK_METHOD2(prepareFrame, void(bool, bool));
     MOCK_METHOD1(dequeueBuffer, std::shared_ptr<renderengine::ExternalTexture>(base::unique_fd*));
-    MOCK_METHOD1(queueBuffer, void(base::unique_fd));
+    MOCK_METHOD(void, queueBuffer, (base::unique_fd, float), (override));
     MOCK_METHOD0(onPresentDisplayCompleted, void());
     MOCK_CONST_METHOD1(dump, void(std::string& result));
     MOCK_CONST_METHOD0(supportsCompositionStrategyPrediction, bool());
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index 002177b..d87eae3 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,44 @@
     return mRefreshStartTime;
 }
 
+namespace {
+void offloadOutputs(Outputs& outputs) {
+    if (!FlagManager::getInstance().multithreaded_present() || outputs.size() < 2) {
+        return;
+    }
+
+    ui::PhysicalDisplayVector<compositionengine::Output*> outputsToOffload;
+    for (const auto& output : outputs) {
+        if (!ftl::Optional(output->getDisplayId()).and_then(HalDisplayId::tryCast)) {
+            // Not HWC-enabled, so it is always client-composited. No need to offload.
+            continue;
+        }
+        if (!output->getState().isEnabled) {
+            continue;
+        }
+
+        // Only run present in multiple threads if all HWC-enabled displays
+        // being refreshed support it.
+        if (!output->supportsOffloadPresent()) {
+            return;
+        }
+        outputsToOffload.push_back(output.get());
+    }
+
+    if (outputsToOffload.size() < 2) {
+        return;
+    }
+
+    // Leave the last eligible display on the main thread, which will
+    // allow it to run concurrently without an extra thread hop.
+    outputsToOffload.pop_back();
+
+    for (compositionengine::Output* output : outputsToOffload) {
+        output->offloadPresentNextFrame();
+    }
+}
+} // namespace
+
 void CompositionEngine::present(CompositionRefreshArgs& args) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
@@ -105,8 +144,23 @@
         }
     }
 
+    // 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.
+    offloadOutputs(args.outputs);
+
+    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..690d35f 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -57,6 +57,7 @@
     mId = args.id;
     mPowerAdvisor = args.powerAdvisor;
     editState().isSecure = args.isSecure;
+    editState().isProtected = args.isProtected;
     editState().displaySpace.setBounds(args.pixels);
     setName(args.name);
 }
@@ -430,4 +431,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..09c7c99 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(
@@ -1149,7 +1176,7 @@
             updateProtectedContentState();
             dequeueRenderBuffer(&bufferFence, &buffer);
             static_cast<void>(composeSurfaces(dirtyRegion, buffer, bufferFence));
-            mRenderSurface->queueBuffer(base::unique_fd());
+            mRenderSurface->queueBuffer(base::unique_fd(), getHdrSdrRatio(buffer));
         }
     }
 
@@ -1197,7 +1224,7 @@
                 std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
     }
     // swap buffers (presentation)
-    mRenderSurface->queueBuffer(std::move(*optReadyFence));
+    mRenderSurface->queueBuffer(std::move(*optReadyFence), getHdrSdrRatio(buffer));
 }
 
 void Output::updateProtectedContentState() {
@@ -1205,10 +1232,18 @@
     auto& renderEngine = getCompositionEngine().getRenderEngine();
     const bool supportsProtectedContent = renderEngine.supportsProtectedContent();
 
-    // If we the display is secure, protected content support is enabled, and at
-    // least one layer has protected content, we need to use a secure back
-    // buffer.
-    if (outputState.isSecure && supportsProtectedContent) {
+    bool isProtected;
+    if (FlagManager::getInstance().display_protected()) {
+        isProtected = outputState.isProtected;
+    } else {
+        isProtected = outputState.isSecure;
+    }
+
+    // We need to set the render surface as protected (DRM) if all the following conditions are met:
+    // 1. The display is protected (in legacy, check if the display is secure)
+    // 2. Protected content is supported
+    // 3. At least one layer has protected content.
+    if (isProtected && supportsProtectedContent) {
         auto layers = getOutputLayersOrderedByZ();
         bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) {
             return layer->getLayerFE().getCompositionState()->hasProtectedContent;
@@ -1263,7 +1298,7 @@
     ALOGV("hasClientComposition");
 
     renderengine::DisplaySettings clientCompositionDisplay =
-            generateClientCompositionDisplaySettings();
+            generateClientCompositionDisplaySettings(tex);
 
     // Generate the client composition requests for the layers on this output.
     auto& renderEngine = getCompositionEngine().getRenderEngine();
@@ -1344,7 +1379,8 @@
     return base::unique_fd(fence->dup());
 }
 
-renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const {
+renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings(
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer) const {
     const auto& outputState = getState();
 
     renderengine::DisplaySettings clientCompositionDisplay;
@@ -1364,8 +1400,10 @@
             : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
     clientCompositionDisplay.maxLuminance =
             mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
-    clientCompositionDisplay.targetLuminanceNits =
-            outputState.clientTargetBrightness * outputState.displayBrightnessNits;
+
+    float hdrSdrRatioMultiplier = 1.0f / getHdrSdrRatio(buffer);
+    clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetBrightness *
+            outputState.displayBrightnessNits * hdrSdrRatioMultiplier;
     clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
     clientCompositionDisplay.renderIntent =
             static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
@@ -1448,12 +1486,16 @@
                                              BlurRegionsOnly
                                    : LayerFE::ClientCompositionTargetSettings::BlurSetting::
                                              Enabled);
+                bool isProtected = supportsProtectedContent;
+                if (FlagManager::getInstance().display_protected()) {
+                    isProtected = outputState.isProtected && supportsProtectedContent;
+                }
                 compositionengine::LayerFE::ClientCompositionTargetSettings
                         targetSettings{.clip = clip,
                                        .needsFiltering = layer->needsFiltering() ||
                                                outputState.needsFiltering,
                                        .isSecure = outputState.isSecure,
-                                       .supportsProtectedContent = supportsProtectedContent,
+                                       .isProtected = isProtected,
                                        .viewport = outputState.layerStackSpace.getContent(),
                                        .dataspace = outputDataspace,
                                        .realContentIsVisible = realContentIsVisible,
@@ -1600,8 +1642,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 +1665,7 @@
     uint64_t outputLayerHash = getState().outputLayerHash;
     editState().lastOutputLayerHash = outputLayerHash;
 
-    if (!getState().isEnabled || !mHwComposerAsyncWorker) {
+    if (!getState().isEnabled || !mPredictCompositionStrategy) {
         ALOGV("canPredictCompositionStrategy disabled");
         return false;
     }
@@ -1669,5 +1718,25 @@
     return mMustRecompose;
 }
 
+float Output::getHdrSdrRatio(const std::shared_ptr<renderengine::ExternalTexture>& buffer) const {
+    if (buffer == nullptr) {
+        return 1.0f;
+    }
+
+    if (!FlagManager::getInstance().fp16_client_target()) {
+        return 1.0f;
+    }
+
+    if (getState().displayBrightnessNits < 0.0f || getState().sdrWhitePointNits <= 0.0f ||
+        buffer->getPixelFormat() != PIXEL_FORMAT_RGBA_FP16 ||
+        (static_cast<int32_t>(getState().dataspace) &
+         static_cast<int32_t>(ui::Dataspace::RANGE_MASK)) !=
+                static_cast<int32_t>(ui::Dataspace::RANGE_EXTENDED)) {
+        return 1.0f;
+    }
+
+    return getState().displayBrightnessNits / getState().sdrWhitePointNits;
+}
+
 } // namespace impl
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index 0fe55db..c0b23d9 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -198,7 +198,7 @@
     return mTexture;
 }
 
-void RenderSurface::queueBuffer(base::unique_fd readyFence) {
+void RenderSurface::queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) {
     auto& state = mDisplay.getState();
 
     if (state.usesClientComposition || state.flipClientTarget) {
@@ -241,7 +241,7 @@
         }
     }
 
-    status_t result = mDisplaySurface->advanceFrame();
+    status_t result = mDisplaySurface->advanceFrame(hdrSdrRatio);
     if (result != NO_ERROR) {
         ALOGE("[%s] failed pushing new frame to HWC: %d", mDisplay.getName().c_str(), result);
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 579c6ba..869dda6 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -184,7 +184,7 @@
             targetSettings{.clip = Region(viewport),
                            .needsFiltering = false,
                            .isSecure = outputState.isSecure,
-                           .supportsProtectedContent = false,
+                           .isProtected = false,
                            .viewport = viewport,
                            .dataspace = outputDataspace,
                            .realContentIsVisible = true,
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 13b6307..a18397d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -20,6 +20,7 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
+#include <common/FlagManager.h>
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
@@ -50,8 +51,19 @@
     for (size_t i = 0; i < incomingLayers.size(); i++) {
         // Checking the IDs here is very strict, but we do this as otherwise we may mistakenly try
         // to access destroyed OutputLayers later on.
-        if (incomingLayers[i]->getId() != existingLayers[i]->getId() ||
-            incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
+        if (incomingLayers[i]->getId() != existingLayers[i]->getId()) {
+            return false;
+        }
+
+        // Do not unflatten if source crop is only moved.
+        if (FlagManager::getInstance().cache_if_source_crop_layer_only_moved() &&
+            incomingLayers[i]->isSourceCropSizeEqual(*(existingLayers[i])) &&
+            incomingLayers[i]->getDifferingFields(*(existingLayers[i])) ==
+                    LayerStateField::SourceCrop) {
+            continue;
+        }
+
+        if (incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
             return false;
         }
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 8dab6ce..0e3fdbb 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -76,6 +76,11 @@
     return hash;
 }
 
+bool LayerState::isSourceCropSizeEqual(const LayerState& other) const {
+    return mSourceCrop.get().getWidth() == other.mSourceCrop.get().getWidth() &&
+            mSourceCrop.get().getHeight() == other.mSourceCrop.get().getHeight();
+}
+
 ftl::Flags<LayerStateField> LayerState::getDifferingFields(const LayerState& other) const {
     ftl::Flags<LayerStateField> differences;
     auto myFields = getNonUniqueFields();
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 60ed660..da578e2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -14,18 +14,25 @@
  * limitations under the License.
  */
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/CompositionEngine.h>
 #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>
+
+using namespace com::android::graphics::surfaceflinger;
+
 namespace android::compositionengine {
 namespace {
 
@@ -107,10 +114,17 @@
     EXPECT_CALL(*mOutput2, prepare(Ref(mRefreshArgs), _));
     EXPECT_CALL(*mOutput3, prepare(Ref(mRefreshArgs), _));
 
+    // All of mOutput<i> are StrictMocks. If the flag is true, it will introduce
+    // calls to getDisplayId, which are not relevant to this test.
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
+
     // 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);
@@ -260,5 +274,214 @@
     EXPECT_TRUE(mEngine.needsAnotherUpdate());
 }
 
+struct CompositionEngineOffloadTest : public testing::Test {
+    impl::CompositionEngine mEngine;
+    CompositionRefreshArgs mRefreshArgs;
+
+    std::shared_ptr<mock::Output> mDisplay1{std::make_shared<StrictMock<mock::Output>>()};
+    std::shared_ptr<mock::Output> mDisplay2{std::make_shared<StrictMock<mock::Output>>()};
+    std::shared_ptr<mock::Output> mVirtualDisplay{std::make_shared<StrictMock<mock::Output>>()};
+    std::shared_ptr<mock::Output> mHalVirtualDisplay{std::make_shared<StrictMock<mock::Output>>()};
+
+    static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(123u);
+    static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(234u);
+    static constexpr GpuVirtualDisplayId kGpuVirtualDisplayId{789u};
+    static constexpr HalVirtualDisplayId kHalVirtualDisplayId{456u};
+
+    std::array<impl::OutputCompositionState, 4> mOutputStates;
+
+    void SetUp() override {
+        EXPECT_CALL(*mDisplay1, getDisplayId)
+                .WillRepeatedly(Return(std::make_optional<DisplayId>(kDisplayId1)));
+        EXPECT_CALL(*mDisplay2, getDisplayId)
+                .WillRepeatedly(Return(std::make_optional<DisplayId>(kDisplayId2)));
+        EXPECT_CALL(*mVirtualDisplay, getDisplayId)
+                .WillRepeatedly(Return(std::make_optional<DisplayId>(kGpuVirtualDisplayId)));
+        EXPECT_CALL(*mHalVirtualDisplay, getDisplayId)
+                .WillRepeatedly(Return(std::make_optional<DisplayId>(kHalVirtualDisplayId)));
+
+        // Most tests will depend on the outputs being enabled.
+        for (auto& state : mOutputStates) {
+            state.isEnabled = true;
+        }
+
+        EXPECT_CALL(*mDisplay1, getState).WillRepeatedly(ReturnRef(mOutputStates[0]));
+        EXPECT_CALL(*mDisplay2, getState).WillRepeatedly(ReturnRef(mOutputStates[1]));
+        EXPECT_CALL(*mVirtualDisplay, getState).WillRepeatedly(ReturnRef(mOutputStates[2]));
+        EXPECT_CALL(*mHalVirtualDisplay, getState).WillRepeatedly(ReturnRef(mOutputStates[3]));
+    }
+
+    void setOutputs(std::initializer_list<std::shared_ptr<mock::Output>> outputs) {
+        for (auto& output : outputs) {
+            // If we call mEngine.present, prepare and present will be called on all the
+            // outputs in mRefreshArgs, but that's not the interesting part of the test.
+            EXPECT_CALL(*output, prepare(Ref(mRefreshArgs), _)).Times(1);
+            EXPECT_CALL(*output, present(Ref(mRefreshArgs)))
+                    .WillOnce(Return(ftl::yield<std::monostate>({})));
+
+            mRefreshArgs.outputs.push_back(std::move(output));
+        }
+    }
+};
+
+TEST_F(CompositionEngineOffloadTest, basic) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnSupport) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(false));
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).Times(0);
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnSupport2) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(false));
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnFlag) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).Times(0);
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).Times(0);
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
+    setOutputs({mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, oneDisplay) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).Times(0);
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, virtualDisplay) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2, mVirtualDisplay});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, virtualDisplay2) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mVirtualDisplay});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, halVirtual) {
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mHalVirtualDisplay});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, ordering) {
+    EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+    EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+
+    EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mVirtualDisplay, mHalVirtualDisplay, mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnEnabled) {
+    // Disable mDisplay2.
+    mOutputStates[1].isEnabled = false;
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+
+    // This is not actually called, because it is not enabled, but this distinguishes
+    // from the case where it did not return true.
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillRepeatedly(Return(true));
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2});
+
+    mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, disabledDisplaysDoNotPreventOthersFromOffloading) {
+    // Disable mDisplay2.
+    mOutputStates[1].isEnabled = false;
+    EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+
+    // This is not actually called, because it is not enabled, but this distinguishes
+    // from the case where it did not return true.
+    EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillRepeatedly(Return(true));
+    EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+
+    EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+    EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+    EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+    setOutputs({mDisplay1, mDisplay2, mHalVirtualDisplay});
+
+    mEngine.present(mRefreshArgs);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 4a778d4..a95a5c6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -936,7 +936,7 @@
     mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
 
     // We expect no calls to queueBuffer if composition was skipped.
-    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
+    EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(1);
 
     // Expect a call to signal no expensive rendering since there is no client composition.
     EXPECT_CALL(mPowerAdvisor, setExpensiveRenderingExpected(DEFAULT_DISPLAY_ID, false));
@@ -957,7 +957,7 @@
     gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
 
     // We expect no calls to queueBuffer if composition was skipped.
-    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0);
+    EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(0);
     EXPECT_CALL(*renderSurface, beginFrame(false));
 
     gpuDisplay->editState().isEnabled = true;
@@ -978,7 +978,7 @@
     gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
 
     // We expect no calls to queueBuffer if composition was skipped.
-    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0);
+    EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(0);
     EXPECT_CALL(*renderSurface, beginFrame(false));
 
     gpuDisplay->editState().isEnabled = true;
@@ -999,7 +999,7 @@
     gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
 
     // We expect a single call to queueBuffer when composition is not skipped.
-    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
+    EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(1);
     EXPECT_CALL(*renderSurface, beginFrame(true));
 
     gpuDisplay->editState().isEnabled = true;
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index b2491d8..8b736be 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -57,9 +57,10 @@
     MOCK_METHOD(status_t, getDeviceCompositionChanges,
                 (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
                  Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
-    MOCK_METHOD5(setClientTarget,
-                 status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
-                          ui::Dataspace));
+    MOCK_METHOD(status_t, setClientTarget,
+                (HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&, ui::Dataspace,
+                 float),
+                (override));
     MOCK_METHOD2(presentAndGetReleaseFences,
                  status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
     MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index f74ef4c..7253354 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -38,11 +38,10 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
-    MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+    MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 5537fcd..bf7ed87 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <android-base/stringprintf.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -35,7 +36,10 @@
 
 #include <cmath>
 #include <cstdint>
+#include <variant>
 
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
 #include "CallOrderStateMachineHelper.h"
 #include "MockHWC2.h"
 #include "RegionMatcher.h"
@@ -43,6 +47,8 @@
 namespace android::compositionengine {
 namespace {
 
+using namespace com::android::graphics::surfaceflinger;
+
 using testing::_;
 using testing::ByMove;
 using testing::ByRef;
@@ -54,6 +60,7 @@
 using testing::Invoke;
 using testing::IsEmpty;
 using testing::Mock;
+using testing::NiceMock;
 using testing::Pointee;
 using testing::Property;
 using testing::Ref;
@@ -2959,7 +2966,7 @@
     EXPECT_CALL(mOutput, updateProtectedContentState());
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _));
-    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
     EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, prepareFrame());
 
@@ -2988,11 +2995,15 @@
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+        EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
+        EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
     }
 
     StrictMock<OutputPartialMock> mOutput;
     mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
     mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    StrictMock<mock::CompositionEngine> mCompositionEngine;
+    StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
 };
 
 TEST_F(OutputFinishFrameTest, ifNotEnabledDoesNothing) {
@@ -3020,7 +3031,34 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
-    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, queuesBufferWithHdrSdrRatio) {
+    SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    auto texture = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_FP16,
+                                                             GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_SW_READ_OFTEN),
+                                     mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    mOutput.mState.displayBrightnessNits = 400.f;
+    mOutput.mState.sdrWhitePointNits = 200.f;
+    mOutput.mState.dataspace = ui::Dataspace::V0_SCRGB;
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _))
+            .WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
 
     impl::GpuCompositionResult result;
     mOutput.finishFrame(std::move(result));
@@ -3030,7 +3068,7 @@
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
     InSequence seq;
-    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
     mOutput.finishFrame(std::move(result));
@@ -3052,7 +3090,7 @@
                 composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer,
                                 Eq(ByRef(result.fence))))
             .WillOnce(Return(ByMove(base::unique_fd())));
-    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
     mOutput.finishFrame(std::move(result));
 }
 
@@ -3322,6 +3360,7 @@
     static constexpr float kDefaultAvgLuminance = 0.7f;
     static constexpr float kDefaultMinLuminance = 0.1f;
     static constexpr float kDisplayLuminance = 400.f;
+    static constexpr float kWhitePointLuminance = 300.f;
     static constexpr float kClientTargetLuminanceNits = 200.f;
     static constexpr float kClientTargetBrightness = 0.5f;
 
@@ -3632,7 +3671,7 @@
     OutputComposeSurfacesTest_UsesExpectedDisplaySettings() {
         EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
         EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, _))
                 .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
@@ -3659,6 +3698,14 @@
           : public CallOrderStateMachineHelper<TestType, OutputWithDisplayBrightnessNits> {
         auto withDisplayBrightnessNits(float nits) {
             getInstance()->mOutput.mState.displayBrightnessNits = nits;
+            return nextState<OutputWithSdrWhitePointNits>();
+        }
+    };
+
+    struct OutputWithSdrWhitePointNits
+          : public CallOrderStateMachineHelper<TestType, OutputWithSdrWhitePointNits> {
+        auto withSdrWhitePointNits(float nits) {
+            getInstance()->mOutput.mState.sdrWhitePointNits = nits;
             return nextState<OutputWithDimmingStage>();
         }
     };
@@ -3688,6 +3735,35 @@
             // May be called zero or one times.
             EXPECT_CALL(getInstance()->mOutput, getSkipColorTransform())
                     .WillRepeatedly(Return(skip));
+            return nextState<PixelFormatState>();
+        }
+    };
+
+    struct PixelFormatState : public CallOrderStateMachineHelper<TestType, PixelFormatState> {
+        auto withPixelFormat(std::optional<PixelFormat> format) {
+            // May be called zero or one times.
+            if (format) {
+                auto outputBuffer = std::make_shared<
+                        renderengine::impl::
+                                ExternalTexture>(sp<GraphicBuffer>::
+                                                         make(1u, 1u, *format,
+                                                              GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                      GRALLOC_USAGE_SW_READ_OFTEN),
+                                                 getInstance()->mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                                 READABLE |
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+                EXPECT_CALL(*getInstance()->mRenderSurface, dequeueBuffer(_))
+                        .WillRepeatedly(Return(outputBuffer));
+            }
+            return nextState<DataspaceState>();
+        }
+    };
+
+    struct DataspaceState : public CallOrderStateMachineHelper<TestType, DataspaceState> {
+        auto withDataspace(ui::Dataspace dataspace) {
+            getInstance()->mOutput.mState.dataspace = dataspace;
             return nextState<ExpectDisplaySettingsState>();
         }
     };
@@ -3709,10 +3785,13 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3736,10 +3815,13 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3763,11 +3845,14 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(
                     aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3791,9 +3876,12 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3816,10 +3904,13 @@
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(false)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3842,10 +3933,13 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3868,10 +3962,13 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(false)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(false)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3895,10 +3992,13 @@
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
             .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
             .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
             .withRenderIntent(
                     aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
             .andIfSkipColorTransform(true)
+            .withPixelFormat(std::nullopt)
+            .withDataspace(kDefaultOutputDataspace)
             .thenExpectDisplaySettingsUsed(
                     {.physicalDisplay = kDefaultOutputDestinationClip,
                      .clip = kDefaultOutputViewport,
@@ -3917,6 +4017,38 @@
             .expectAFenceWasReturned();
 }
 
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       usesExpectedDisplaySettingsWithFp16Buffer) {
+    SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
+    ALOGE("alecmouri: %d", flags::fp16_client_target());
+    verify().ifMixedCompositionIs(false)
+            .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kDisplayLuminance)
+            .withSdrWhitePointNits(kWhitePointLuminance)
+            .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+            .withRenderIntent(
+                    aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
+            .andIfSkipColorTransform(true)
+            .withPixelFormat(PIXEL_FORMAT_RGBA_FP16)
+            .withDataspace(ui::Dataspace::V0_SCRGB)
+            .thenExpectDisplaySettingsUsed(
+                    {.physicalDisplay = kDefaultOutputDestinationClip,
+                     .clip = kDefaultOutputViewport,
+                     .maxLuminance = kDefaultMaxLuminance,
+                     .currentLuminanceNits = kDisplayLuminance,
+                     .outputDataspace = ui::Dataspace::V0_SCRGB,
+                     .colorTransform = kDefaultColorTransformMat,
+                     .deviceHandlesColorTransform = true,
+                     .orientation = kDefaultOutputOrientationFlags,
+                     .targetLuminanceNits = kClientTargetLuminanceNits * 0.75f,
+                     .dimmingStage =
+                             aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+                     .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+                             COLORIMETRIC})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
 struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeSurfacesTest {
     struct Layer {
         Layer() {
@@ -4900,5 +5032,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/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index 83937a6..edfaa26 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -263,9 +263,9 @@
     state.flipClientTarget = false;
 
     EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
-    EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+    EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
 
-    mSurface.queueBuffer(base::unique_fd());
+    mSurface.queueBuffer(base::unique_fd(), 0.5f);
 
     EXPECT_EQ(buffer.get(), mSurface.mutableTextureForTest().get());
 }
@@ -283,9 +283,9 @@
     EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
     EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
             .WillOnce(Return(NO_ERROR));
-    EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+    EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
 
-    mSurface.queueBuffer(base::unique_fd());
+    mSurface.queueBuffer(base::unique_fd(), 0.5f);
 
     EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
 }
@@ -303,9 +303,9 @@
     EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
     EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
             .WillOnce(Return(NO_ERROR));
-    EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+    EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
 
-    mSurface.queueBuffer(base::unique_fd());
+    mSurface.queueBuffer(base::unique_fd(), 0.5f);
 
     EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
 }
@@ -323,9 +323,9 @@
                     DoAll(SetArgPointee<0>(buffer.get()), SetArgPointee<1>(-1), Return(NO_ERROR)));
     EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getNativeBuffer(), -1))
             .WillOnce(Return(NO_ERROR));
-    EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+    EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
 
-    mSurface.queueBuffer(base::unique_fd());
+    mSurface.queueBuffer(base::unique_fd(), 0.5f);
 
     EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
 }
@@ -345,9 +345,9 @@
     EXPECT_CALL(mDisplay, isVirtual()).WillOnce(Return(true));
     EXPECT_CALL(*mNativeWindow, cancelBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
             .WillOnce(Return(NO_ERROR));
-    EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+    EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
 
-    mSurface.queueBuffer(base::unique_fd());
+    mSurface.queueBuffer(base::unique_fd(), 0.5f);
 
     EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
 }
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index d9318af..763b998 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <compositionengine/impl/planner/Flattener.h>
@@ -239,6 +242,30 @@
     expectAllLayersFlattened(layers);
 }
 
+TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_if_source_crop_layer_only_moved,
+                      true);
+
+    auto& layerState1 = mTestLayers[0]->layerState;
+    auto& layerState2 = mTestLayers[1]->layerState;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTestLayers[0]->outputLayerCompositionState.sourceCrop = FloatRect{0.f, 0.f, 100.f, 100.f};
+    mTestLayers[1]->outputLayerCompositionState.sourceCrop = FloatRect{8.f, 16.f, 108.f, 116.f};
+
+    // only source crop is moved, so no flatten
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+}
+
 TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
     auto& layerState1 = mTestLayers[0]->layerState;
     auto& layerState2 = mTestLayers[1]->layerState;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 2ffe92b..950b05e 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -63,15 +63,16 @@
         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) {
+        mHasDesiredModeTrace(concatId("HasDesiredMode"), false) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
+    mCompositionDisplay->editState().isProtected = args.isProtected;
     mCompositionDisplay->createRenderSurface(
             compositionengine::RenderSurfaceCreationArgsBuilder()
                     .setDisplayWidth(ANativeWindow_getWidth(args.nativeWindow.get()))
@@ -210,31 +211,28 @@
 }
 
 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) {
-    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()
-                           : "null",
-              to_string(getId()).c_str());
-        return BAD_VALUE;
-    }
-    mUpcomingActiveMode = info;
+bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
+                                       const hal::VsyncPeriodChangeConstraints& constraints,
+                                       hal::VsyncPeriodChangeTimeline& outTimeline) {
+    mPendingModeOpt = std::move(desiredMode);
     mIsModeSetPending = true;
 
-    const auto& pendingMode = *info.modeOpt->modePtr;
-    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getVsyncRate().getIntValue());
+    const auto& mode = *mPendingModeOpt->mode.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,59 +522,59 @@
     }
 }
 
-auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
-        -> DesiredActiveModeAction {
+auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force)
+        -> DesiredModeAction {
     ATRACE_CALL();
 
-    LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided");
-    LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.modeOpt->modePtr->getPhysicalDisplayId(),
+    const auto& desiredModePtr = desiredMode.mode.modePtr;
+
+    LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(),
                         "DisplayId mismatch");
 
-    ALOGV("%s(%s)", __func__, to_string(*info.modeOpt->modePtr).c_str());
+    ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).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 (mDesiredModeOpt) {
+        // A mode transition was already scheduled, so just override the desired mode.
+        const bool emitEvent = mDesiredModeOpt->emitEvent;
+        mDesiredModeOpt = std::move(desiredMode);
+        mDesiredModeOpt->emitEvent |= emitEvent;
+        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() == desiredModePtr->getId()) {
+        if (activeMode == desiredMode.mode) {
+            return DesiredModeAction::None;
         }
 
-        setActiveMode(desiredMode.getId(), desiredMode.getVsyncRate(), info.modeOpt->fps);
-        return DesiredActiveModeAction::InitiateRenderRateSwitch;
+        // ...but the render rate changed:
+        setActiveMode(desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
+                      desiredMode.mode.fps);
+        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;
+    mDesiredModeOpt = std::move(desiredMode);
+    mHasDesiredModeTrace = true;
+    return DesiredModeAction::InitiateDisplayModeSwitch;
 }
 
-std::optional<DisplayDevice::ActiveModeInfo> DisplayDevice::getDesiredActiveMode() const {
-    std::scoped_lock lock(mActiveModeLock);
-    if (mDesiredActiveModeChanged) return mDesiredActiveMode;
-    return std::nullopt;
+auto DisplayDevice::getDesiredMode() const -> DisplayModeRequestOpt {
+    std::scoped_lock lock(mDesiredModeLock);
+    return mDesiredModeOpt;
 }
 
-void DisplayDevice::clearDesiredActiveModeState() {
-    std::scoped_lock lock(mActiveModeLock);
-    mDesiredActiveMode.event = scheduler::DisplayModeEvent::None;
-    mDesiredActiveModeChanged = false;
+void DisplayDevice::clearDesiredMode() {
+    std::scoped_lock lock(mDesiredModeLock);
+    mDesiredModeOpt.reset();
+    mHasDesiredModeTrace = false;
 }
 
 void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index a40f310..ac390cb 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;
@@ -185,39 +186,19 @@
      * Display mode management.
      */
 
-    // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest.
-    struct ActiveModeInfo {
-        using Event = scheduler::DisplayModeEvent;
+    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
 
-        ActiveModeInfo() = default;
-        ActiveModeInfo(scheduler::FrameRateMode mode, Event event)
-              : modeOpt(std::move(mode)), event(event) {}
+    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false)
+            EXCLUDES(mDesiredModeLock);
 
-        explicit ActiveModeInfo(display::DisplayModeRequest&& request)
-              : ActiveModeInfo(std::move(request.mode),
-                               request.emitEvent ? Event::Changed : Event::None) {}
+    using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
 
-        ftl::Optional<scheduler::FrameRateMode> modeOpt;
-        Event event = Event::None;
+    DisplayModeRequestOpt getDesiredMode() const EXCLUDES(mDesiredModeLock);
+    void clearDesiredMode() EXCLUDES(mDesiredModeLock);
 
-        bool operator!=(const ActiveModeInfo& other) const {
-            return modeOpt != other.modeOpt || event != other.event;
-        }
-    };
-
-    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;
+    DisplayModeRequestOpt getPendingMode() const REQUIRES(kMainThreadContext) {
+        return mPendingModeOpt;
     }
-
     bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; }
 
     scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
@@ -226,9 +207,8 @@
 
     void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
 
-    status_t initiateModeChange(const ActiveModeInfo&,
-                                const hal::VsyncPeriodChangeConstraints& constraints,
-                                hal::VsyncPeriodChangeTimeline* outTimeline)
+    bool initiateModeChange(display::DisplayModeRequest&&, const hal::VsyncPeriodChangeConstraints&,
+                            hal::VsyncPeriodChangeTimeline& outTimeline)
             REQUIRES(kMainThreadContext);
 
     void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
@@ -282,9 +262,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 +299,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;
+    DisplayModeRequestOpt mDesiredModeOpt GUARDED_BY(mDesiredModeLock);
+    TracedOrdinal<bool> mHasDesiredModeTrace GUARDED_BY(mDesiredModeLock);
 
-    ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
+    DisplayModeRequestOpt mPendingModeOpt GUARDED_BY(kMainThreadContext);
     bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
 };
 
@@ -352,6 +332,7 @@
     uint32_t height = 0;
     std::string displayName;
     bool isSecure = false;
+    bool isProtected = false;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
     Fps requestedRefreshRate;
 
@@ -373,6 +354,7 @@
 
     int32_t sequenceId{0};
     bool isSecure{false};
+    bool isProtected{false};
     sp<ANativeWindow> nativeWindow;
     sp<compositionengine::DisplaySurface> displaySurface;
     ui::Rotation physicalOrientation{ui::ROTATION_0};
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 2d957e6..c25f9dd 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -78,6 +78,7 @@
 
 using AidlColorTransform = aidl::android::hardware::graphics::common::ColorTransform;
 using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
+using AidlDisplayHotplugEvent = aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using AidlFRect = aidl::android::hardware::graphics::common::FRect;
 using AidlRect = aidl::android::hardware::graphics::common::Rect;
 using AidlTransform = aidl::android::hardware::graphics::common::Transform;
@@ -174,9 +175,9 @@
     AidlIComposerCallbackWrapper(HWC2::ComposerCallback& callback) : mCallback(callback) {}
 
     ::ndk::ScopedAStatus onHotplug(int64_t in_display, bool in_connected) override {
-        const auto connection = in_connected ? V2_4::IComposerCallback::Connection::CONNECTED
-                                             : V2_4::IComposerCallback::Connection::DISCONNECTED;
-        mCallback.onComposerHalHotplug(translate<Display>(in_display), connection);
+        const auto event = in_connected ? AidlDisplayHotplugEvent::CONNECTED
+                                        : AidlDisplayHotplugEvent::DISCONNECTED;
+        mCallback.onComposerHalHotplugEvent(translate<Display>(in_display), event);
         return ::ndk::ScopedAStatus::ok();
     }
 
@@ -216,6 +217,12 @@
         return ::ndk::ScopedAStatus::ok();
     }
 
+    ::ndk::ScopedAStatus onHotplugEvent(int64_t in_display,
+                                        AidlDisplayHotplugEvent event) override {
+        mCallback.onComposerHalHotplugEvent(translate<Display>(in_display), event);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
 private:
     HWC2::ComposerCallback& mCallback;
 };
@@ -659,7 +666,8 @@
 
 Error AidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                                     int acquireFence, Dataspace dataspace,
-                                    const std::vector<IComposerClient::Rect>& damage) {
+                                    const std::vector<IComposerClient::Rect>& damage,
+                                    float hdrSdrRatio) {
     const native_handle_t* handle = nullptr;
     if (target.get()) {
         handle = target->getNativeBuffer()->handle;
@@ -672,7 +680,7 @@
                 .setClientTarget(translate<int64_t>(display), slot, handle, acquireFence,
                                  translate<aidl::android::hardware::graphics::common::Dataspace>(
                                          dataspace),
-                                 translate<AidlRect>(damage));
+                                 translate<AidlRect>(damage), hdrSdrRatio);
     } else {
         error = Error::BAD_DISPLAY;
     }
@@ -1575,8 +1583,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 +1593,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/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 1635a16..51ac1f5 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -124,7 +124,8 @@
      */
     Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                           int acquireFence, Dataspace dataspace,
-                          const std::vector<IComposerClient::Rect>& damage) override;
+                          const std::vector<IComposerClient::Rect>& damage,
+                          float hdrSdrRatio) override;
     Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
     Error setColorTransform(Display display, const float* matrix) override;
     Error setOutputBuffer(Display display, const native_handle_t* buffer,
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 082717a..1a24222 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -163,7 +163,8 @@
      */
     virtual Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                                   int acquireFence, Dataspace dataspace,
-                                  const std::vector<IComposerClient::Rect>& damage) = 0;
+                                  const std::vector<IComposerClient::Rect>& damage,
+                                  float hdrSdrRatio) = 0;
     virtual Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) = 0;
     virtual Error setColorTransform(Display display, const float* matrix) = 0;
     virtual Error setOutputBuffer(Display display, const native_handle_t* buffer,
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index ce602a8..c77cdd4 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -91,7 +91,7 @@
     return NO_ERROR;
 }
 
-status_t FramebufferSurface::advanceFrame() {
+status_t FramebufferSurface::advanceFrame(float hdrSdrRatio) {
     Mutex::Autolock lock(mMutex);
 
     BufferItem item;
@@ -131,7 +131,7 @@
         hwcBuffer = mCurrentBuffer; // HWC hasn't previously seen this buffer in this slot
     }
     status_t result = mHwc.setClientTarget(mDisplayId, mCurrentBufferSlot, mCurrentFence, hwcBuffer,
-                                           mDataspace);
+                                           mDataspace, hdrSdrRatio);
     if (result != NO_ERROR) {
         ALOGE("error posting framebuffer: %s (%d)", strerror(-result), result);
         return result;
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 0b863da..2728cf6 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -46,7 +46,7 @@
 
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType compositionType);
-    virtual status_t advanceFrame();
+    virtual status_t advanceFrame(float hdrSdrRatio);
     virtual void onFrameCommitted();
     virtual void dumpAsString(String8& result) const;
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index bc763b2..24a9e22 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -446,12 +446,13 @@
 }
 
 Error Display::setClientTarget(uint32_t slot, const sp<GraphicBuffer>& target,
-        const sp<Fence>& acquireFence, Dataspace dataspace)
-{
+                               const sp<Fence>& acquireFence, Dataspace dataspace,
+                               float hdrSdrRatio) {
     // TODO: Properly encode client target surface damage
     int32_t fenceFd = acquireFence->dup();
-    auto intError = mComposer.setClientTarget(mId, slot, target,
-            fenceFd, dataspace, std::vector<Hwc2::IComposerClient::Rect>());
+    auto intError =
+            mComposer.setClientTarget(mId, slot, target, fenceFd, dataspace,
+                                      std::vector<Hwc2::IComposerClient::Rect>(), hdrSdrRatio);
     return static_cast<Error>(intError);
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index e7f807f..f907061 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -38,6 +38,7 @@
 #include "Hal.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
 #include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Color.h>
@@ -64,15 +65,16 @@
 
 namespace hal = android::hardware::graphics::composer::hal;
 
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 
 // Implement this interface to receive hardware composer events.
 //
 // These callback functions will generally be called on a hwbinder thread, but
-// when first registering the callback the onComposerHalHotplug() function will
-// immediately be called on the thread calling registerCallback().
+// when first registering the callback the onComposerHalHotplugEvent() function
+// will immediately be called on the thread calling registerCallback().
 struct ComposerCallback {
-    virtual void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) = 0;
+    virtual void onComposerHalHotplugEvent(hal::HWDisplayId, DisplayHotplugEvent) = 0;
     virtual void onComposerHalRefresh(hal::HWDisplayId) = 0;
     virtual void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp,
                                     std::optional<hal::VsyncPeriodNanos>) = 0;
@@ -139,7 +141,8 @@
     [[nodiscard]] virtual hal::Error present(android::sp<android::Fence>* outPresentFence) = 0;
     [[nodiscard]] virtual hal::Error setClientTarget(
             uint32_t slot, const android::sp<android::GraphicBuffer>& target,
-            const android::sp<android::Fence>& acquireFence, hal::Dataspace dataspace) = 0;
+            const android::sp<android::Fence>& acquireFence, hal::Dataspace dataspace,
+            float hdrSdrRatio) = 0;
     [[nodiscard]] virtual hal::Error setColorMode(hal::ColorMode mode,
                                                   hal::RenderIntent renderIntent) = 0;
     [[nodiscard]] virtual hal::Error setColorTransform(const android::mat4& matrix) = 0;
@@ -227,7 +230,7 @@
     hal::Error present(android::sp<android::Fence>* outPresentFence) override;
     hal::Error setClientTarget(uint32_t slot, const android::sp<android::GraphicBuffer>& target,
                                const android::sp<android::Fence>& acquireFence,
-                               hal::Dataspace dataspace) override;
+                               hal::Dataspace dataspace, float hdrSdrRatio) override;
     hal::Error setColorMode(hal::ColorMode, hal::RenderIntent) override;
     hal::Error setColorTransform(const android::mat4& matrix) override;
     hal::Error setOutputBuffer(const android::sp<android::GraphicBuffer>&,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 9aaaa95..6b67865 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -485,12 +485,12 @@
 
 status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot,
                                      const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
-                                     ui::Dataspace dataspace) {
+                                     ui::Dataspace dataspace, float hdrSdrRatio) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     ALOGV("%s for display %s", __FUNCTION__, to_string(displayId).c_str());
     auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
-    auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace);
+    auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace, hdrSdrRatio);
     RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
     return NO_ERROR;
 }
@@ -545,6 +545,7 @@
         }
     }
 
+    ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
         sp<Fence> outPresentFence = Fence::NO_FENCE;
         uint32_t state = UINT32_MAX;
@@ -945,6 +946,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/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 5846c07..af62731 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -151,7 +151,8 @@
             std::optional<DeviceRequestedChanges>* outChanges) = 0;
 
     virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
-                                     const sp<GraphicBuffer>& target, ui::Dataspace) = 0;
+                                     const sp<GraphicBuffer>& target, ui::Dataspace,
+                                     float hdrSdrRatio) = 0;
 
     // Present layers to the display and read releaseFences.
     virtual status_t presentAndGetReleaseFences(
@@ -352,7 +353,8 @@
             std::optional<DeviceRequestedChanges>* outChanges) override;
 
     status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
-                             const sp<GraphicBuffer>& target, ui::Dataspace) override;
+                             const sp<GraphicBuffer>& target, ui::Dataspace,
+                             float hdrSdrRatio) override;
 
     // Present layers to the display and read releaseFences.
     status_t presentAndGetReleaseFences(
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 20f7548..31c2833 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -20,6 +20,7 @@
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <aidl/android/hardware/graphics/common/Hdr.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
@@ -58,6 +59,7 @@
 using ContentType = IComposerClient::ContentType;
 using Capability = IComposer::Capability;
 using ClientTargetProperty = IComposerClient::ClientTargetProperty;
+using DisplayHotplugEvent = aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using DisplayRequest = IComposerClient::DisplayRequest;
 using DisplayType = IComposerClient::DisplayType;
 using HWConfigId = V2_1::Config;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 1e7c6da..5f1d5f8 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -25,6 +25,7 @@
 #include "HidlComposerHal.h"
 
 #include <SurfaceFlingerProperties.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <android/binder_manager.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <hidl/HidlTransportSupport.h>
@@ -38,6 +39,7 @@
 #include <algorithm>
 #include <cinttypes>
 
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using aidl::android::hardware::graphics::common::HdrConversionCapability;
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
@@ -64,8 +66,13 @@
     ComposerCallbackBridge(ComposerCallback& callback, bool vsyncSwitchingSupported)
           : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
 
+    // For code sharing purposes, `ComposerCallback` (implemented by SurfaceFlinger)
+    // replaced `onComposerHalHotplug` with `onComposerHalHotplugEvent` by converting
+    // from HIDL's connection into an AIDL DisplayHotplugEvent.
     Return<void> onHotplug(Display display, Connection connection) override {
-        mCallback.onComposerHalHotplug(display, connection);
+        const auto event = connection == Connection::CONNECTED ? DisplayHotplugEvent::CONNECTED
+                                                               : DisplayHotplugEvent::DISCONNECTED;
+        mCallback.onComposerHalHotplugEvent(display, event);
         return Void();
     }
 
@@ -601,7 +608,8 @@
 
 Error HidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                                     int acquireFence, Dataspace dataspace,
-                                    const std::vector<IComposerClient::Rect>& damage) {
+                                    const std::vector<IComposerClient::Rect>& damage,
+                                    float /*hdrSdrRatio*/) {
     mWriter.selectDisplay(display);
 
     const native_handle_t* handle = nullptr;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 5c19b47..c768d27 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -226,7 +226,8 @@
      */
     Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                           int acquireFence, Dataspace dataspace,
-                          const std::vector<IComposerClient::Rect>& damage) override;
+                          const std::vector<IComposerClient::Rect>& damage,
+                          float hdrSdrRatio) override;
     Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
     Error setColorTransform(Display display, const float* matrix) override;
     Error setOutputBuffer(Display display, const native_handle_t* buffer,
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index f00ef67..dd228b4 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -50,7 +50,6 @@
 namespace impl {
 
 using aidl::android::hardware::power::Boost;
-using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::Mode;
 using aidl::android::hardware::power::SessionHint;
 using aidl::android::hardware::power::WorkDuration;
@@ -144,11 +143,13 @@
     if (!mBootFinished.load()) {
         return;
     }
-    if (usePowerHintSession() && ensurePowerHintSessionRunning()) {
+    if (usePowerHintSession()) {
         std::lock_guard lock(mHintSessionMutex);
-        auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP);
-        if (!ret.isOk()) {
-            mHintSessionRunning = false;
+        if (ensurePowerHintSessionRunning()) {
+            auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP);
+            if (!ret.isOk()) {
+                mHintSession = nullptr;
+            }
         }
     }
 }
@@ -162,11 +163,13 @@
 
     if (mSendUpdateImminent.exchange(false)) {
         ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
-        if (usePowerHintSession() && ensurePowerHintSessionRunning()) {
+        if (usePowerHintSession()) {
             std::lock_guard lock(mHintSessionMutex);
-            auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
-            if (!ret.isOk()) {
-                mHintSessionRunning = false;
+            if (ensurePowerHintSessionRunning()) {
+                auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
+                if (!ret.isOk()) {
+                    mHintSession = nullptr;
+                }
             }
         }
 
@@ -193,14 +196,12 @@
     }
 }
 
-// checks both if it supports and if it's enabled
 bool PowerAdvisor::usePowerHintSession() {
     // uses cached value since the underlying support and flag are unlikely to change at runtime
     return mHintSessionEnabled.value_or(false) && supportsPowerHintSession();
 }
 
 bool PowerAdvisor::supportsPowerHintSession() {
-    // cache to avoid needing lock every time
     if (!mSupportsHintSession.has_value()) {
         mSupportsHintSession = getPowerHal().getHintSessionPreferredRate().isOk();
     }
@@ -208,10 +209,15 @@
 }
 
 bool PowerAdvisor::ensurePowerHintSessionRunning() {
-    if (!mHintSessionRunning && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
-        startPowerHintSession(mHintSessionThreadIds);
+    if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
+        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+                                                   mHintSessionThreadIds, mTargetDuration.ns());
+
+        if (ret.isOk()) {
+            mHintSession = ret.value();
+        }
     }
-    return mHintSessionRunning;
+    return mHintSession != nullptr;
 }
 
 void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
@@ -223,15 +229,16 @@
     {
         mTargetDuration = targetDuration;
         if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
-        if (ensurePowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) {
+        if (targetDuration == mLastTargetDurationSent) return;
+        std::lock_guard lock(mHintSessionMutex);
+        if (ensurePowerHintSessionRunning()) {
             ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
             mLastTargetDurationSent = targetDuration;
-            std::lock_guard lock(mHintSessionMutex);
             auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
             if (!ret.isOk()) {
                 ALOGW("Failed to set power hint target work duration with error: %s",
                       ret.getDescription().c_str());
-                mHintSessionRunning = false;
+                mHintSession = nullptr;
             }
         }
     }
@@ -244,16 +251,12 @@
     }
     ATRACE_CALL();
     std::optional<Duration> actualDuration = estimateWorkDuration();
-    if (!actualDuration.has_value() || actualDuration < 0ns || !ensurePowerHintSessionRunning()) {
+    if (!actualDuration.has_value() || actualDuration < 0ns) {
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
     actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
     mActualDuration = actualDuration;
-    WorkDuration duration;
-    duration.durationNanos = actualDuration->ns();
-    duration.timeStampNanos = TimePoint::now().ns();
-    mHintSessionQueue.push_back(duration);
 
     if (sTraceHintSessionData) {
         ATRACE_INT64("Measured duration", actualDuration->ns());
@@ -269,13 +272,34 @@
           actualDuration->ns(), mLastTargetDurationSent.ns(),
           Duration{*actualDuration - mLastTargetDurationSent}.ns());
 
+    if (mTimingTestingMode) {
+        mDelayReportActualMutexAcquisitonPromise.get_future().wait();
+        mDelayReportActualMutexAcquisitonPromise = std::promise<bool>{};
+    }
+
     {
         std::lock_guard lock(mHintSessionMutex);
+        if (!ensurePowerHintSessionRunning()) {
+            ALOGV("Hint session not running and could not be started, skipping");
+            return;
+        }
+
+        WorkDuration duration{
+                .timeStampNanos = TimePoint::now().ns(),
+                // TODO(b/284324521): Correctly calculate total duration.
+                .durationNanos = actualDuration->ns(),
+                .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+                .cpuDurationNanos = actualDuration->ns(),
+                // TODO(b/284324521): Calculate RenderEngine GPU time.
+                .gpuDurationNanos = 0,
+        };
+        mHintSessionQueue.push_back(duration);
+
         auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
         if (!ret.isOk()) {
             ALOGW("Failed to report actual work durations with error: %s",
                   ret.getDescription().c_str());
-            mHintSessionRunning = false;
+            mHintSession = nullptr;
             return;
         }
     }
@@ -286,7 +310,8 @@
     mHintSessionEnabled = enabled;
 }
 
-bool PowerAdvisor::startPowerHintSession(const std::vector<int32_t>& threadIds) {
+bool PowerAdvisor::startPowerHintSession(std::vector<int32_t>&& threadIds) {
+    mHintSessionThreadIds = threadIds;
     if (!mBootFinished.load()) {
         return false;
     }
@@ -294,25 +319,14 @@
         ALOGI("Cannot start power hint session: disabled or unsupported");
         return false;
     }
-    if (mHintSessionRunning) {
+    LOG_ALWAYS_FATAL_IF(mHintSessionThreadIds.empty(),
+                        "No thread IDs provided to power hint session!");
+    std::lock_guard lock(mHintSessionMutex);
+    if (mHintSession != nullptr) {
         ALOGE("Cannot start power hint session: already running");
         return false;
     }
-    LOG_ALWAYS_FATAL_IF(threadIds.empty(), "No thread IDs provided to power hint session!");
-    {
-        std::lock_guard lock(mHintSessionMutex);
-        mHintSession = nullptr;
-        mHintSessionThreadIds = threadIds;
-
-        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
-                                                   threadIds, mTargetDuration.ns());
-
-        if (ret.isOk()) {
-            mHintSessionRunning = true;
-            mHintSession = ret.value();
-        }
-    }
-    return mHintSessionRunning;
+    return ensurePowerHintSessionRunning();
 }
 
 void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 05e4c8b..0276e44 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -46,16 +46,15 @@
 
     // Initializes resources that cannot be initialized on construction
     virtual void init() = 0;
+    // Used to indicate that power hints can now be reported
     virtual void onBootFinished() = 0;
     virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0;
     virtual bool isUsingExpensiveRendering() = 0;
-    virtual void notifyCpuLoadUp() = 0;
-    virtual void notifyDisplayUpdateImminentAndCpuReset() = 0;
-    // Checks both if it supports and if it's enabled
+    // Checks both if it's supported and if it's enabled; this is thread-safe since its values are
+    // set before onBootFinished, which gates all methods that run on threads other than SF main
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
 
-    virtual bool ensurePowerHintSessionRunning() = 0;
     // Sends a power hint that updates to the target work duration for the frame
     virtual void updateTargetWorkDuration(Duration targetDuration) = 0;
     // Sends a power hint for the actual known work duration at the end of the frame
@@ -63,7 +62,7 @@
     // Sets whether the power hint session is enabled
     virtual void enablePowerHintSession(bool enabled) = 0;
     // Initializes the power hint session
-    virtual bool startPowerHintSession(const std::vector<int32_t>& threadIds) = 0;
+    virtual bool startPowerHintSession(std::vector<int32_t>&& threadIds) = 0;
     // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
     virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
     // Reports the start and end times of a hwc validate call this frame for a given display
@@ -94,6 +93,12 @@
     virtual void setDisplays(std::vector<DisplayId>& displayIds) = 0;
     // Sets the target duration for the entire pipeline including the gpu
     virtual void setTotalFrameTargetWorkDuration(Duration targetDuration) = 0;
+
+    // --- The following methods may run on threads besides SF main ---
+    // Send a hint about an upcoming increase in the CPU workload
+    virtual void notifyCpuLoadUp() = 0;
+    // Send a hint about the imminent start of a new CPU workload
+    virtual void notifyDisplayUpdateImminentAndCpuReset() = 0;
 };
 
 namespace impl {
@@ -109,16 +114,13 @@
     void onBootFinished() override;
     void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override;
     bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
-    void notifyCpuLoadUp() override;
-    void notifyDisplayUpdateImminentAndCpuReset() override;
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
-    bool ensurePowerHintSessionRunning() override;
     void updateTargetWorkDuration(Duration targetDuration) override;
     void reportActualWorkDuration() override;
     void enablePowerHintSession(bool enabled) override;
-    bool startPowerHintSession(const std::vector<int32_t>& threadIds) override;
-    void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime);
+    bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+    void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) override;
     void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
                               TimePoint validateEndTime) override;
     void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
@@ -128,13 +130,16 @@
     void setExpectedPresentTime(TimePoint expectedPresentTime) override;
     void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override;
     void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override;
-
     void setFrameDelay(Duration frameDelayDuration) override;
     void setCommitStart(TimePoint commitStartTime) override;
     void setCompositeEnd(TimePoint compositeEndTime) override;
     void setDisplays(std::vector<DisplayId>& displayIds) override;
     void setTotalFrameTargetWorkDuration(Duration targetDuration) override;
 
+    // --- The following methods may run on threads besides SF main ---
+    void notifyCpuLoadUp() override;
+    void notifyDisplayUpdateImminentAndCpuReset() override;
+
 private:
     friend class PowerAdvisorTest;
 
@@ -220,6 +225,7 @@
     // this normalizes them together and takes the max of the two
     Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
 
+    bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
     std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
 
     // Current frame's delay
@@ -242,9 +248,10 @@
     // Ensure powerhal connection is initialized
     power::PowerHalController& getPowerHal();
 
+    // These variables are set before mBootFinished and never mutated after, so it's safe to access
+    // from threaded methods.
     std::optional<bool> mHintSessionEnabled;
     std::optional<bool> mSupportsHintSession;
-    bool mHintSessionRunning = false;
 
     std::mutex mHintSessionMutex;
     std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
@@ -261,6 +268,11 @@
     // The list of thread ids, stored so we can restart the session from this class if needed
     std::vector<int32_t> mHintSessionThreadIds;
     Duration mLastTargetDurationSent = kDefaultTargetDuration;
+
+    // Used to manage the execution ordering of reportActualWorkDuration for concurrency testing
+    std::promise<bool> mDelayReportActualMutexAcquisitonPromise;
+    bool mTimingTestingMode = false;
+
     // Whether we should emit ATRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index d62075e..4b5a68c 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -175,7 +175,7 @@
     return NO_ERROR;
 }
 
-status_t VirtualDisplaySurface::advanceFrame() {
+status_t VirtualDisplaySurface::advanceFrame(float hdrSdrRatio) {
     if (GpuVirtualDisplayId::tryCast(mDisplayId)) {
         return NO_ERROR;
     }
@@ -223,7 +223,7 @@
         }
         // TODO: Correctly propagate the dataspace from GL composition
         result = mHwc.setClientTarget(*halDisplayId, mFbProducerSlot, mFbFence, hwcBuffer,
-                                      ui::Dataspace::UNKNOWN);
+                                      ui::Dataspace::UNKNOWN, hdrSdrRatio);
     }
 
     return result;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index be06e2b..90426f7 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -84,7 +84,7 @@
     //
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType);
-    virtual status_t advanceFrame();
+    virtual status_t advanceFrame(float hdrSdrRatio);
     virtual void onFrameCommitted();
     virtual void dumpAsString(String8& result) const;
     virtual void resizeBuffers(const ui::Size&) override;
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index e55cd3e..55b395b 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -18,41 +18,26 @@
 #include "DisplayDevice.h"
 
 namespace android {
-namespace {
-
-RenderArea::RotationFlags applyDeviceOrientation(bool useIdentityTransform,
-                                                 const DisplayDevice& display) {
-    if (!useIdentityTransform) {
-        return RenderArea::RotationFlags::ROT_0;
-    }
-
-    return ui::Transform::toRotationFlags(display.getOrientation());
-}
-
-} // namespace
 
 std::unique_ptr<RenderArea> DisplayRenderArea::create(wp<const DisplayDevice> displayWeak,
                                                       const Rect& sourceCrop, ui::Size reqSize,
                                                       ui::Dataspace reqDataSpace,
-                                                      bool useIdentityTransform,
                                                       bool hintForSeamlessTransition,
                                                       bool allowSecureLayers) {
     if (auto display = displayWeak.promote()) {
         // Using new to access a private constructor.
         return std::unique_ptr<DisplayRenderArea>(
                 new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace,
-                                      useIdentityTransform, hintForSeamlessTransition,
-                                      allowSecureLayers));
+                                      hintForSeamlessTransition, allowSecureLayers));
     }
     return nullptr;
 }
 
 DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
                                      ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                     bool useIdentityTransform, bool hintForSeamlessTransition,
-                                     bool allowSecureLayers)
+                                     bool hintForSeamlessTransition, bool allowSecureLayers)
       : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)),
+                   allowSecureLayers),
         mDisplay(std::move(display)),
         mSourceCrop(sourceCrop) {}
 
@@ -73,17 +58,7 @@
     if (mSourceCrop.isEmpty()) {
         return mDisplay->getLayerStackSpaceRect();
     }
-
-    // Correct for the orientation when the screen capture request contained
-    // useIdentityTransform. This will cause the rotation flag to be non 0 since
-    // it needs to rotate based on the screen orientation to allow the screenshot
-    // to be taken in the ROT_0 orientation
-    const auto flags = getRotationFlags();
-    int width = mDisplay->getLayerStackSpaceRect().getWidth();
-    int height = mDisplay->getLayerStackSpaceRect().getHeight();
-    ui::Transform rotation;
-    rotation.set(flags, width, height);
-    return rotation.transform(mSourceCrop);
+    return mSourceCrop;
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 9a4981c..4555a9e 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -29,7 +29,6 @@
 public:
     static std::unique_ptr<RenderArea> create(wp<const DisplayDevice>, const Rect& sourceCrop,
                                               ui::Size reqSize, ui::Dataspace,
-                                              bool useIdentityTransform,
                                               bool hintForSeamlessTransition,
                                               bool allowSecureLayers = true);
 
@@ -40,8 +39,7 @@
 
 private:
     DisplayRenderArea(sp<const DisplayDevice>, const Rect& sourceCrop, ui::Size reqSize,
-                      ui::Dataspace, bool useIdentityTransform, bool hintForSeamlessTransition,
-                      bool allowSecureLayers = true);
+                      ui::Dataspace, bool hintForSeamlessTransition, bool allowSecureLayers = true);
 
     const sp<const DisplayDevice> mDisplay;
     const Rect mSourceCrop;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 9dc3938..803299c 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -280,6 +280,19 @@
     return protoJank;
 }
 
+FrameTimelineEvent::JankSeverityType toProto(JankSeverityType jankSeverityType) {
+    switch (jankSeverityType) {
+        case JankSeverityType::Unknown:
+            return FrameTimelineEvent::SEVERITY_UNKNOWN;
+        case JankSeverityType::None:
+            return FrameTimelineEvent::SEVERITY_NONE;
+        case JankSeverityType::Partial:
+            return FrameTimelineEvent::SEVERITY_PARTIAL;
+        case JankSeverityType::Full:
+            return FrameTimelineEvent::SEVERITY_FULL;
+    }
+}
+
 // Returns the smallest timestamp from the set of predictions and actuals.
 nsecs_t getMinTime(PredictionState predictionState, TimelineItem predictions,
                    TimelineItem actuals) {
@@ -389,6 +402,15 @@
     return mJankType;
 }
 
+std::optional<JankSeverityType> SurfaceFrame::getJankSeverityType() const {
+    std::scoped_lock lock(mMutex);
+    if (mActuals.presentTime == 0) {
+        // Frame hasn't been presented yet.
+        return std::nullopt;
+    }
+    return mJankSeverityType;
+}
+
 nsecs_t SurfaceFrame::getBaseTime() const {
     std::scoped_lock lock(mMutex);
     return getMinTime(mPredictionState, mPredictions, mActuals);
@@ -505,10 +527,11 @@
 }
 
 void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                                      nsecs_t& deadlineDelta) {
+                                      Fps displayFrameRenderRate, nsecs_t& deadlineDelta) {
     if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
         // Cannot do any classification for invalid present time.
         mJankType = JankType::Unknown;
+        mJankSeverityType = JankSeverityType::Unknown;
         deadlineDelta = -1;
         return;
     }
@@ -519,6 +542,7 @@
         // reasonable app, so prediction expire would mean a huge scheduling delay.
         mJankType = mPresentState != PresentState::Presented ? JankType::Dropped
                                                              : JankType::AppDeadlineMissed;
+        mJankSeverityType = JankSeverityType::Unknown;
         deadlineDelta = -1;
         return;
     }
@@ -543,6 +567,11 @@
     if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
         mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
                                                  : FramePresentMetadata::EarlyPresent;
+        // Jank that is missing by less than the render rate period is classified as partial jank,
+        // otherwise it is a full jank.
+        mJankSeverityType = std::abs(presentDelta) < displayFrameRenderRate.getPeriodNsecs()
+                ? JankSeverityType::Partial
+                : JankSeverityType::Full;
     } else {
         mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
     }
@@ -613,6 +642,7 @@
         mJankType = JankType::Dropped;
         // Since frame was not presented, lets drop any present value
         mActuals.presentTime = 0;
+        mJankSeverityType = JankSeverityType::Unknown;
     }
 }
 
@@ -625,7 +655,7 @@
     mActuals.presentTime = presentTime;
     nsecs_t deadlineDelta = 0;
 
-    classifyJankLocked(displayFrameJankType, refreshRate, deadlineDelta);
+    classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, deadlineDelta);
 
     if (mPredictionState != PredictionState::None) {
         // Only update janky frames if the app used vsync predictions
@@ -718,6 +748,7 @@
         actualSurfaceFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
         actualSurfaceFrameStartEvent->set_prediction_type(toProto(mPredictionState));
         actualSurfaceFrameStartEvent->set_is_buffer(mIsBuffer);
+        actualSurfaceFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
     // Actual timeline end
@@ -910,6 +941,7 @@
         // Cannot do jank classification with expired predictions or invalid signal times. Set the
         // deltas to 0 as both negative and positive deltas are used as real values.
         mJankType = JankType::Unknown;
+        mJankSeverityType = JankSeverityType::Unknown;
         deadlineDelta = 0;
         deltaToVsync = 0;
         if (!presentTimeValid) {
@@ -941,6 +973,11 @@
     if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
         mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
                                                  : FramePresentMetadata::EarlyPresent;
+        // Jank that is missing by less than the render rate period is classified as partial jank,
+        // otherwise it is a full jank.
+        mJankSeverityType = std::abs(presentDelta) < mRenderRate.getPeriodNsecs()
+                ? JankSeverityType::Partial
+                : JankSeverityType::Full;
     } else {
         mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
     }
@@ -1119,6 +1156,7 @@
             actualDisplayFrameStartEvent->set_prediction_type(toProto(PredictionState::Valid));
             actualDisplayFrameStartEvent->set_present_type(FrameTimelineEvent::PRESENT_DROPPED);
             actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(JankType::Dropped));
+            actualDisplayFrameStartEvent->set_jank_severity_type(toProto(JankSeverityType::None));
         });
 
         // Actual timeline end
@@ -1160,6 +1198,7 @@
         actualDisplayFrameStartEvent->set_gpu_composition(mGpuFence != FenceTime::NO_FENCE);
         actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
         actualDisplayFrameStartEvent->set_prediction_type(toProto(mPredictionState));
+        actualDisplayFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
     // Actual timeline end
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 6598e21..b5047a3 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -168,6 +168,7 @@
     // Returns std::nullopt if the frame hasn't been classified yet.
     // Used by both SF and FrameTimeline.
     std::optional<int32_t> getJankType() const;
+    std::optional<JankSeverityType> getJankSeverityType() const;
 
     // Functions called by SF
     int64_t getToken() const { return mToken; };
@@ -232,7 +233,7 @@
     void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
     void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
     void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                            nsecs_t& deadlineDelta) REQUIRES(mMutex);
+                            Fps displayFrameRenderRate, nsecs_t& deadlineDelta) REQUIRES(mMutex);
 
     const int64_t mToken;
     const int32_t mInputEventId;
@@ -252,6 +253,8 @@
     mutable std::mutex mMutex;
     // Bitmask for the type of jank
     int32_t mJankType GUARDED_BY(mMutex) = JankType::None;
+    // Enum for the severity of jank
+    JankSeverityType mJankSeverityType GUARDED_BY(mMutex) = JankSeverityType::None;
     // Indicates if this frame was composited by the GPU or not
     bool mGpuComposition GUARDED_BY(mMutex) = false;
     // Refresh rate for this frame.
@@ -404,6 +407,7 @@
         FramePresentMetadata getFramePresentMetadata() const { return mFramePresentMetadata; };
         FrameReadyMetadata getFrameReadyMetadata() const { return mFrameReadyMetadata; };
         int32_t getJankType() const { return mJankType; }
+        JankSeverityType getJankSeverityType() const { return mJankSeverityType; }
         const std::vector<std::shared_ptr<SurfaceFrame>>& getSurfaceFrames() const {
             return mSurfaceFrames;
         }
@@ -435,6 +439,8 @@
         PredictionState mPredictionState = PredictionState::None;
         // Bitmask for the type of jank
         int32_t mJankType = JankType::None;
+        // Enum for the severity of jank
+        JankSeverityType mJankSeverityType = JankSeverityType::None;
         // A valid gpu fence indicates that the DisplayFrame was composited by the GPU
         std::shared_ptr<FenceTime> mGpuFence = FenceTime::NO_FENCE;
         // Enum for the type of present
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..ad5e42b 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.
@@ -708,7 +708,7 @@
     ftl::Flags<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes &
             (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
              RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata |
-             RequestedLayerState::Changes::AffectsChildren |
+             RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Input |
              RequestedLayerState::Changes::FrameRate | RequestedLayerState::Changes::GameMode);
     snapshot.changes |= parentChanges;
     if (args.displayChanges) snapshot.changes |= RequestedLayerState::Changes::Geometry;
@@ -814,9 +814,12 @@
                 RequestedLayerState::Changes::Hierarchy) ||
         snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
                              RequestedLayerState::Changes::Hierarchy)) {
-        bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+        const bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
                 scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
-        if (!requested.requestedFrameRate.isValid() || shouldOverrideChildren) {
+        const bool propagationAllowed = parentSnapshot.frameRateSelectionStrategy !=
+                scheduler::LayerInfo::FrameRateSelectionStrategy::Self;
+        if ((!requested.requestedFrameRate.isValid() && propagationAllowed) ||
+            shouldOverrideChildren) {
             snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
         } else {
             snapshot.inheritedFrameRate = requested.requestedFrameRate;
@@ -828,12 +831,15 @@
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
-        const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
-                requested.frameRateSelectionStrategy);
-        snapshot.frameRateSelectionStrategy =
-                strategy == scheduler::LayerInfo::FrameRateSelectionStrategy::Self
-                ? parentSnapshot.frameRateSelectionStrategy
-                : strategy;
+        if (parentSnapshot.frameRateSelectionStrategy ==
+            scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren) {
+            snapshot.frameRateSelectionStrategy =
+                    scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+        } else {
+            const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                    requested.frameRateSelectionStrategy);
+            snapshot.frameRateSelectionStrategy = strategy;
+        }
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
@@ -1035,6 +1041,19 @@
 
     snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
     snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+    snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
+            ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
+            : parentSnapshot.inputInfo.touchOcclusionMode;
+    if (requested.dropInputMode == gui::DropInputMode::ALL ||
+        parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
+        snapshot.dropInputMode = gui::DropInputMode::ALL;
+    } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED ||
+               parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) {
+        snapshot.dropInputMode = gui::DropInputMode::OBSCURED;
+    } else {
+        snapshot.dropInputMode = gui::DropInputMode::NONE;
+    }
+
     updateVisibility(snapshot, snapshot.isVisible);
     if (!needsInputInfo(snapshot, requested)) {
         return;
@@ -1058,18 +1077,6 @@
     }
 
     snapshot.inputInfo.alpha = snapshot.color.a;
-    snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
-            ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
-            : parentSnapshot.inputInfo.touchOcclusionMode;
-    if (requested.dropInputMode == gui::DropInputMode::ALL ||
-        parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
-        snapshot.dropInputMode = gui::DropInputMode::ALL;
-    } else if (requested.dropInputMode == gui::DropInputMode::OBSCURED ||
-               parentSnapshot.dropInputMode == gui::DropInputMode::OBSCURED) {
-        snapshot.dropInputMode = gui::DropInputMode::OBSCURED;
-    } else {
-        snapshot.dropInputMode = gui::DropInputMode::NONE;
-    }
 
     handleDropInputMode(snapshot, parentSnapshot);
 
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..209df79 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;
@@ -126,7 +125,7 @@
     frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
     frameRateCategorySmoothSwitchOnly = false;
     frameRateSelectionStrategy =
-            static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     dataspace = ui::Dataspace::V0_SRGB;
     gameMode = gui::GameMode::Unsupported;
     requestedFrameRate = {};
@@ -418,6 +417,8 @@
     if (!obj.handleAlive) out << " handleNotAlive";
     if (obj.requestedFrameRate.isValid())
         out << " requestedFrameRate: {" << obj.requestedFrameRate << "}";
+    if (obj.dropInputMode != gui::DropInputMode::NONE)
+        out << " dropInputMode=" << static_cast<uint32_t>(obj.dropInputMode);
     return out;
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 4c2da91..f7800bb 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -193,7 +193,7 @@
     mDrawingState.dropInputMode = gui::DropInputMode::NONE;
     mDrawingState.dimmingEnabled = true;
     mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
-    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Self;
+    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Propagate;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -1273,14 +1273,15 @@
     auto now = systemTime();
     *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate, now);
 
-    // The frame rate is propagated to the children
+    // The frame rate is propagated to the children by default, but some properties may override it.
     bool childrenHaveFrameRate = false;
+    const bool overrideChildrenFrameRate = overrideChildren || shouldOverrideChildrenFrameRate();
+    const bool canPropagateFrameRate = shouldPropagateFrameRate() || overrideChildrenFrameRate;
     for (const sp<Layer>& child : mCurrentChildren) {
         childrenHaveFrameRate |=
-                child->propagateFrameRateForLayerTree(frameRate,
-                                                      overrideChildren ||
-                                                              shouldOverrideChildrenFrameRate(),
-                                                      transactionNeeded);
+                child->propagateFrameRateForLayerTree(canPropagateFrameRate ? frameRate
+                                                                            : FrameRate(),
+                                                      overrideChildrenFrameRate, transactionNeeded);
     }
 
     // If we don't have a valid frame rate specification, but the children do, we set this
@@ -1718,10 +1719,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, ' ');
     }
@@ -3255,7 +3264,7 @@
 
     // If the layer had been updated a TextureView, this would make sure the present time could be
     // same to TextureView update when it's a small dirty, and get the correct heuristic rate.
-    if (mFlinger->mScheduler->supportSmallDirtyDetection()) {
+    if (mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
         if (mDrawingState.useVsyncIdForRefreshRateSelection) {
             mUsedVsyncIdForRefreshRateSelection = true;
         }
@@ -3288,7 +3297,7 @@
             }
         }
 
-        if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
+        if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
             return static_cast<nsecs_t>(0);
         }
 
@@ -4326,7 +4335,6 @@
         prepareBasicGeometryCompositionState();
         prepareGeometryCompositionState();
         snapshot->roundedCorner = getRoundedCornerState();
-        snapshot->stretchEffect = getStretchEffect();
         snapshot->transformedBounds = mScreenBounds;
         if (mEffectiveShadowRadius > 0.f) {
             snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings;
@@ -4432,7 +4440,7 @@
 void Layer::setIsSmallDirty(const Region& damageRegion,
                             const ui::Transform& layerToDisplayTransform) {
     mSmallDirty = false;
-    if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
+    if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
         return;
     }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 28168c3..c772e0e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -1178,6 +1178,10 @@
                 FrameRateSelectionStrategy::OverrideChildren;
     }
 
+    bool shouldPropagateFrameRate() const {
+        return getDrawingState().frameRateSelectionStrategy != FrameRateSelectionStrategy::Self;
+    }
+
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
     // a transform from the current layer coordinate space to display(screen) coordinate space.
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index f25619a..2dbcb84 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -208,9 +208,15 @@
         // activeBuffer, then we need to return LayerSettings.
         return;
     }
-    const bool blackOutLayer =
-            (mSnapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) ||
-            ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) && !targetSettings.isSecure);
+    bool blackOutLayer;
+    if (FlagManager::getInstance().display_protected()) {
+        blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
+                (mSnapshot->isSecure && !targetSettings.isSecure);
+    } else {
+        blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
+                ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) &&
+                 !targetSettings.isSecure);
+    }
     const bool bufferCanBeUsedAsHwTexture =
             mSnapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
     if (blackOutLayer || !bufferCanBeUsedAsHwTexture) {
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 8f658d5..c888ccc 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -276,13 +276,11 @@
     }
 
     const Rect sampledBounds = sampleRegion.bounds();
-    constexpr bool kUseIdentityTransform = false;
     constexpr bool kHintForSeamlessTransition = false;
 
     SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] {
         return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(),
-                                         ui::Dataspace::V0_SRGB, kUseIdentityTransform,
-                                         kHintForSeamlessTransition);
+                                         ui::Dataspace::V0_SRGB, kHintForSeamlessTransition);
     });
 
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
@@ -376,10 +374,11 @@
 
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
+    constexpr bool kIsProtected = false;
 
     if (const auto fenceResult =
                 mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
-                                             kRegionSampling, kGrayscale, nullptr)
+                                             kRegionSampling, kGrayscale, kIsProtected, nullptr)
                         .get();
         fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 71b85bd..5de148e 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -18,20 +18,16 @@
 // physical render area.
 class RenderArea {
 public:
-    using RotationFlags = ui::Transform::RotationFlags;
-
     enum class CaptureFill {CLEAR, OPAQUE};
 
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
-               bool hintForSeamlessTransition, bool allowSecureLayers = false,
-               RotationFlags rotation = ui::Transform::ROT_0)
+               bool hintForSeamlessTransition, bool allowSecureLayers = false)
           : mAllowSecureLayers(allowSecureLayers),
             mReqSize(reqSize),
             mReqDataSpace(reqDataSpace),
             mCaptureFill(captureFill),
-            mRotationFlags(rotation),
             mHintForSeamlessTransition(hintForSeamlessTransition) {}
 
     static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
@@ -72,9 +68,6 @@
     // on the display).
     virtual Rect getSourceCrop() const = 0;
 
-    // Returns the rotation of the source crop and the layers.
-    RotationFlags getRotationFlags() const { return mRotationFlags; }
-
     // Returns the size of the physical render area.
     int getReqWidth() const { return mReqSize.width; }
     int getReqHeight() const { return mReqSize.height; }
@@ -103,7 +96,6 @@
     const ui::Size mReqSize;
     const ui::Dataspace mReqDataSpace;
     const CaptureFill mCaptureFill;
-    const RotationFlags mRotationFlags;
     const bool mHintForSeamlessTransition;
 };
 
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/FrameRateCompatibility.h b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
index 405c982..d8c408f 100644
--- a/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
+++ b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
@@ -29,6 +29,8 @@
     ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the
                      // content properly. Any other value will result in a pull down.
 
+    Gte, // Layer needs greater than or equal to the frame rate.
+
     NoVote, // Layer doesn't have any requirements for the refresh rate and
             // should not be considered when the display refresh rate is determined.
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index d309adc..9c00302 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -278,6 +278,8 @@
                         return LayerVoteType::NoVote;
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
+                    case Layer::FrameRateCompatibility::Gte:
+                        return LayerVoteType::ExplicitGte;
                 }
             }();
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index bf3a7bc..97fca39 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -276,17 +276,16 @@
 
     if (const auto averageFrameTime = calculateAverageFrameTime()) {
         const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
-        const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
-        if (refreshRateConsistent) {
-            const auto knownRefreshRate = selector.findClosestKnownFrameRate(refreshRate);
+        const auto closestKnownRefreshRate = mRefreshRateHistory.add(refreshRate, now, selector);
+        if (closestKnownRefreshRate.isValid()) {
             using fps_approx_ops::operator!=;
 
             // To avoid oscillation, use the last calculated refresh rate if it is close enough.
             if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
                         MARGIN &&
-                mLastRefreshRate.reported != knownRefreshRate) {
+                mLastRefreshRate.reported != closestKnownRefreshRate) {
                 mLastRefreshRate.calculated = refreshRate;
-                mLastRefreshRate.reported = knownRefreshRate;
+                mLastRefreshRate.reported = closestKnownRefreshRate;
             }
 
             ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
@@ -432,7 +431,8 @@
     mRefreshRates.clear();
 }
 
-bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
+Fps LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now,
+                                       const RefreshRateSelector& selector) {
     mRefreshRates.push_back({refreshRate, now});
     while (mRefreshRates.size() >= HISTORY_SIZE ||
            now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
@@ -447,11 +447,11 @@
         ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
-    return isConsistent();
+    return selectRefreshRate(selector);
 }
 
-bool LayerInfo::RefreshRateHistory::isConsistent() const {
-    if (mRefreshRates.empty()) return true;
+Fps LayerInfo::RefreshRateHistory::selectRefreshRate(const RefreshRateSelector& selector) const {
+    if (mRefreshRates.empty()) return Fps();
 
     const auto [min, max] =
             std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(),
@@ -459,8 +459,19 @@
                                     return isStrictlyLess(lhs.refreshRate, rhs.refreshRate);
                                 });
 
-    const bool consistent =
-            max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
+    const auto maxClosestRate = selector.findClosestKnownFrameRate(max->refreshRate);
+    const bool consistent = [&](Fps maxFps, Fps minFps) {
+        if (FlagManager::getInstance().use_known_refresh_rate_for_fps_consistency()) {
+            if (maxFps.getValue() - minFps.getValue() <
+                MARGIN_CONSISTENT_FPS_FOR_CLOSEST_REFRESH_RATE) {
+                const auto minClosestRate = selector.findClosestKnownFrameRate(minFps);
+                using fps_approx_ops::operator==;
+                return maxClosestRate == minClosestRate;
+            }
+            return false;
+        }
+        return maxFps.getValue() - minFps.getValue() < MARGIN_CONSISTENT_FPS;
+    }(max->refreshRate, min->refreshRate);
 
     if (CC_UNLIKELY(sTraceEnabled)) {
         if (!mHeuristicTraceTagData.has_value()) {
@@ -472,7 +483,7 @@
         ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
-    return consistent;
+    return consistent ? maxClosestRate : Fps();
 }
 
 FrameRateCompatibility LayerInfo::FrameRate::convertCompatibility(int8_t compatibility) {
@@ -485,6 +496,8 @@
             return FrameRateCompatibility::Exact;
         case ANATIVEWINDOW_FRAME_RATE_MIN:
             return FrameRateCompatibility::Min;
+        case ANATIVEWINDOW_FRAME_RATE_GTE:
+            return FrameRateCompatibility::Gte;
         case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
             return FrameRateCompatibility::NoVote;
         default:
@@ -526,10 +539,12 @@
 LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
         int8_t strategy) {
     switch (strategy) {
-        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
-            return FrameRateSelectionStrategy::Self;
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE:
+            return FrameRateSelectionStrategy::Propagate;
         case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
             return FrameRateSelectionStrategy::OverrideChildren;
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+            return FrameRateSelectionStrategy::Self;
         default:
             LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
             return FrameRateSelectionStrategy::Self;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index d24fc33..50bb83d 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -81,10 +81,11 @@
     using RefreshRateVotes = ftl::SmallVector<LayerInfo::LayerVote, 2>;
 
     enum class FrameRateSelectionStrategy {
-        Self,
+        Propagate,
         OverrideChildren,
+        Self,
 
-        ftl_last = OverrideChildren
+        ftl_last = Self
     };
 
     // Encapsulates the frame rate specifications of the layer. This information will be used
@@ -264,8 +265,8 @@
         // Clears History
         void clear();
 
-        // Adds a new refresh rate and returns true if it is consistent
-        bool add(Fps refreshRate, nsecs_t now);
+        // Adds a new refresh rate and returns valid refresh rate if it is consistent enough
+        Fps add(Fps refreshRate, nsecs_t now, const RefreshRateSelector&);
 
     private:
         friend class LayerHistoryTest;
@@ -285,13 +286,14 @@
             std::string average;
         };
 
-        bool isConsistent() const;
+        Fps selectRefreshRate(const RefreshRateSelector&) const;
         HeuristicTraceTagData makeHeuristicTraceTagData() const;
 
         const std::string mName;
         mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
         std::deque<RefreshRateData> mRefreshRates;
         static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
+        static constexpr float MARGIN_CONSISTENT_FPS_FOR_CLOSEST_REFRESH_RATE = 5.0;
     };
 
     // Represents whether we were able to determine either layer is frequent or infrequent
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 47c8ef9..e06221a 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>
@@ -333,6 +332,15 @@
         return calculateNonExactMatchingDefaultLayerScoreLocked(displayPeriod, layerPeriod);
     }
 
+    if (layer.vote == LayerVoteType::ExplicitGte) {
+        using fps_approx_ops::operator>=;
+        if (refreshRate >= layer.desiredRefreshRate) {
+            return 1.0f;
+        } else {
+            return calculateDistanceScoreLocked(layer.desiredRefreshRate, refreshRate);
+        }
+    }
+
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
         layer.vote == LayerVoteType::Heuristic) {
         using fps_approx_ops::operator<;
@@ -391,13 +399,20 @@
     return 0;
 }
 
-float RefreshRateSelector::calculateDistanceScoreFromMax(Fps refreshRate) const {
-    const auto& maxFps = mAppRequestFrameRates.back().fps;
-    const float ratio = refreshRate.getValue() / maxFps.getValue();
-    // Use ratio^2 to get a lower score the more we get further from peak
+float RefreshRateSelector::calculateDistanceScoreLocked(Fps referenceRate, Fps refreshRate) const {
+    using fps_approx_ops::operator>=;
+    const float ratio = referenceRate >= refreshRate
+            ? refreshRate.getValue() / referenceRate.getValue()
+            : referenceRate.getValue() / refreshRate.getValue();
+    // Use ratio^2 to get a lower score the more we get further from the reference rate.
     return ratio * ratio;
 }
 
+float RefreshRateSelector::calculateDistanceScoreFromMaxLocked(Fps refreshRate) const {
+    const auto& maxFps = mAppRequestFrameRates.back().fps;
+    return calculateDistanceScoreLocked(maxFps, refreshRate);
+}
+
 float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
                                                      bool isSeamlessSwitch) const {
     // Slightly prefer seamless switches.
@@ -422,7 +437,7 @@
 
     // If the layer wants Max, give higher score to the higher refresh rate
     if (layer.vote == LayerVoteType::Max) {
-        return calculateDistanceScoreFromMax(refreshRate);
+        return calculateDistanceScoreFromMaxLocked(refreshRate);
     }
 
     if (layer.vote == LayerVoteType::ExplicitExact) {
@@ -490,6 +505,7 @@
     int explicitDefaultVoteLayers = 0;
     int explicitExactOrMultipleVoteLayers = 0;
     int explicitExact = 0;
+    int explicitGteLayers = 0;
     int explicitCategoryVoteLayers = 0;
     int seamedFocusedLayers = 0;
     int categorySmoothSwitchOnlyLayers = 0;
@@ -514,6 +530,9 @@
             case LayerVoteType::ExplicitExact:
                 explicitExact++;
                 break;
+            case LayerVoteType::ExplicitGte:
+                explicitGteLayers++;
+                break;
             case LayerVoteType::ExplicitCategory:
                 explicitCategoryVoteLayers++;
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
@@ -536,7 +555,7 @@
     }
 
     const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
-            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 ||
+            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 || explicitGteLayers > 0 ||
             explicitCategoryVoteLayers > 0;
 
     const Policy* policy = getCurrentPolicyLocked();
@@ -689,6 +708,7 @@
                     case LayerVoteType::Max:
                     case LayerVoteType::ExplicitDefault:
                     case LayerVoteType::ExplicitExact:
+                    case LayerVoteType::ExplicitGte:
                     case LayerVoteType::ExplicitCategory:
                         return false;
                 }
@@ -971,17 +991,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()) {
@@ -1078,7 +1102,7 @@
             return;
         }
 
-        float score = calculateDistanceScoreFromMax(frameRateMode.fps);
+        float score = calculateDistanceScoreFromMaxLocked(frameRateMode.fps);
 
         if (ascending) {
             score = 1.0f / score;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 545b939..a1a7c28 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>
@@ -42,13 +39,6 @@
 
 using namespace std::chrono_literals;
 
-enum class DisplayModeEvent : unsigned { None = 0b0, Changed = 0b1 };
-
-inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) {
-    using T = std::underlying_type_t<DisplayModeEvent>;
-    return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
-}
-
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
 // Selects the refresh rate of a display by ranking its `DisplayModes` in accordance with
@@ -152,6 +142,7 @@
                                  // ExactOrMultiple compatibility
         ExplicitExact,           // Specific refresh rate that was provided by the app with
                                  // Exact compatibility
+        ExplicitGte,             // Greater than or equal to frame rate provided by the app
         ExplicitCategory,        // Specific frame rate category was provided by the app
 
         ftl_last = ExplicitCategory
@@ -258,9 +249,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);
 
@@ -464,7 +454,11 @@
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
     // Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1.
-    float calculateDistanceScoreFromMax(Fps refreshRate) const REQUIRES(mLock);
+    float calculateDistanceScoreFromMaxLocked(Fps refreshRate) const REQUIRES(mLock);
+
+    // Returns the refresh rate score based on its distance from the reference rate.
+    float calculateDistanceScoreLocked(Fps referenceRate, Fps refreshRate) const REQUIRES(mLock);
+
     // calculates a score for a layer. Used to determine the display refresh rate
     // and the frame rate override for certains applications.
     float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index b54f334..aa8d54d 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -92,7 +92,12 @@
     using namespace sysprop;
     using namespace std::string_literals;
 
-    if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {
+    const int32_t defaultTouchTimerValue =
+            FlagManager::getInstance().enable_fro_dependent_features() &&
+                    sysprop::enable_frame_rate_override(true)
+            ? 200
+            : 0;
+    if (const int32_t millis = set_touch_timer_ms(defaultTouchTimerValue); millis > 0) {
         // Touch events are coming to SF every 100ms, so the timer needs to be higher than that
         mTouchTimer.emplace(
                 "TouchTimer", std::chrono::milliseconds(millis),
@@ -118,7 +123,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 +186,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 +220,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 +229,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) {
@@ -497,13 +515,16 @@
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
-    for (const auto& [id, _] : mDisplays) {
-        resyncToHardwareVsyncLocked(id, allowToEnable);
+    for (const auto& [id, display] : mDisplays) {
+        if (display.powerMode != hal::PowerMode::OFF ||
+            !FlagManager::getInstance().multithreaded_present()) {
+            resyncToHardwareVsyncLocked(id, allowToEnable);
+        }
     }
 }
 
 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 +533,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 +584,25 @@
     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)});
+    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
 
-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;
+Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id,
+                                    TimePoint currentExpectedPresentTime) const {
+    std::scoped_lock lock(mDisplayLock);
+    ftl::FakeGuard guard(kMainThreadContext);
+
+    const auto displayOpt = mDisplays.get(id);
+    if (!displayOpt) {
+        ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
+        return Fps{};
     }
+    const Display& display = *displayOpt;
+    const nsecs_t threshold =
+            display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
+    const nsecs_t nextVsyncTime = display.schedulePtr->getTracker().nextAnticipatedVSyncTimeFrom(
+            currentExpectedPresentTime.ns() + threshold);
+    return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
 }
 
 void Scheduler::resync() {
@@ -913,9 +940,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;
 }
@@ -1221,7 +1248,7 @@
 }
 
 void Scheduler::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
-    mSmallAreaDetectionAllowMappings.setThesholdForAppId(appId, threshold);
+    mSmallAreaDetectionAllowMappings.setThresholdForAppId(appId, threshold);
 }
 
 bool Scheduler::isSmallDirtyArea(int32_t appId, uint32_t dirtyArea) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index c78051a..454ef83 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; }
 
@@ -313,6 +313,9 @@
         return pacesetterSelectorPtr()->getActiveMode().fps;
     }
 
+    Fps getNextFrameInterval(PhysicalDisplayId, TimePoint currentExpectedPresentTime) const
+            EXCLUDES(mDisplayLock);
+
     // Returns the framerate of the layer with the given sequence ID
     float getLayerFramerate(nsecs_t now, int32_t id) const {
         return mLayerHistory.getLayerFramerate(now, id);
@@ -320,9 +323,10 @@
 
     bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
 
-    // Returns true if the small dirty detection is enabled.
-    bool supportSmallDirtyDetection() const {
-        return mFeatures.test(Feature::kSmallDirtyContentDetection);
+    // Returns true if the small dirty detection is enabled for the appId.
+    bool supportSmallDirtyDetection(int32_t appId) {
+        return mFeatures.test(Feature::kSmallDirtyContentDetection) &&
+                mSmallAreaDetectionAllowMappings.getThresholdForAppId(appId).has_value();
     }
 
     // Injects a delay that is a fraction of the predicted frame duration for the next frame.
@@ -354,7 +358,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 +435,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 +521,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/SmallAreaDetectionAllowMappings.cpp b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
index 38c6da4..7510ebf 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
@@ -29,7 +29,7 @@
     }
 }
 
-void SmallAreaDetectionAllowMappings::setThesholdForAppId(int32_t appId, float threshold) {
+void SmallAreaDetectionAllowMappings::setThresholdForAppId(int32_t appId, float threshold) {
     if (!isValidThreshold(threshold)) return;
 
     std::lock_guard lock(mLock);
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
index e10301c..4ec5e3b 100644
--- a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
@@ -28,7 +28,7 @@
 
 public:
     void update(std::vector<std::pair<int32_t, float>>& appIdThresholdMappings);
-    void setThesholdForAppId(int32_t appId, float threshold) EXCLUDES(mLock);
+    void setThresholdForAppId(int32_t appId, float threshold) EXCLUDES(mLock);
     std::optional<float> getThresholdForAppId(int32_t uid) EXCLUDES(mLock);
 
 private:
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/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index ef9b457..dd03366 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -16,6 +16,7 @@
 
 #include "ScreenCaptureOutput.h"
 #include "ScreenCaptureRenderSurface.h"
+#include "ui/Rotation.h"
 
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/DisplayColorProfileCreationArgs.h>
@@ -24,24 +25,6 @@
 
 namespace android {
 
-namespace {
-
-ui::Size getDisplaySize(ui::Rotation orientation, const Rect& sourceCrop) {
-    if (orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270) {
-        return {sourceCrop.getHeight(), sourceCrop.getWidth()};
-    }
-    return {sourceCrop.getWidth(), sourceCrop.getHeight()};
-}
-
-Rect getOrientedDisplaySpaceRect(ui::Rotation orientation, int reqWidth, int reqHeight) {
-    if (orientation == ui::Rotation::Rotation90 || orientation == ui::Rotation::Rotation270) {
-        return {reqHeight, reqWidth};
-    }
-    return {reqWidth, reqHeight};
-}
-
-} // namespace
-
 std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs args) {
     std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
             ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
@@ -49,6 +32,7 @@
             bool>(args.compositionEngine, args.renderArea, args.colorProfile, args.regionSampling,
                   args.dimInGammaSpaceForEnhancedScreenshots);
     output->editState().isSecure = args.renderArea.isSecure();
+    output->editState().isProtected = args.isProtected;
     output->setCompositionEnabled(true);
     output->setLayerFilter({args.layerStack});
     output->setRenderSurface(std::make_unique<ScreenCaptureRenderSurface>(std::move(args.buffer)));
@@ -62,11 +46,10 @@
                     .Build()));
 
     const Rect& sourceCrop = args.renderArea.getSourceCrop();
-    const ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
-    output->setDisplaySize(getDisplaySize(orientation, sourceCrop));
+    const ui::Rotation orientation = ui::ROTATION_0;
+    output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
     output->setProjection(orientation, sourceCrop,
-                          getOrientedDisplaySpaceRect(orientation, args.renderArea.getReqWidth(),
-                                                      args.renderArea.getReqHeight()));
+                          {args.renderArea.getReqWidth(), args.renderArea.getReqHeight()});
 
     {
         std::string name = args.regionSampling ? "RegionSampling" : "ScreenCaptureOutput";
@@ -92,10 +75,10 @@
     outputState.renderIntent = mColorProfile.renderIntent;
 }
 
-renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings()
-        const {
+renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings(
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer) const {
     auto clientCompositionDisplay =
-            compositionengine::impl::Output::generateClientCompositionDisplaySettings();
+            compositionengine::impl::Output::generateClientCompositionDisplaySettings(buffer);
     clientCompositionDisplay.clip = mRenderArea.getSourceCrop();
 
     auto renderIntent = static_cast<ui::RenderIntent>(clientCompositionDisplay.renderIntent);
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index fc095de..069f458 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -38,6 +38,7 @@
     bool regionSampling;
     bool treat170mAsSrgb;
     bool dimInGammaSpaceForEnhancedScreenshots;
+    bool isProtected = false;
 };
 
 // ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer.
@@ -58,7 +59,8 @@
 
 protected:
     bool getSkipColorTransform() const override { return false; }
-    renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override;
+    renderengine::DisplaySettings generateClientCompositionDisplaySettings(
+            const std::shared_ptr<renderengine::ExternalTexture>& buffer) const override;
 
 private:
     const RenderArea& mRenderArea;
diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h
index 2097300..50ba9bf 100644
--- a/services/surfaceflinger/ScreenCaptureRenderSurface.h
+++ b/services/surfaceflinger/ScreenCaptureRenderSurface.h
@@ -37,7 +37,7 @@
         return mBuffer;
     }
 
-    void queueBuffer(base::unique_fd readyFence) override {
+    void queueBuffer(base::unique_fd readyFence, float) override {
         mRenderFence = sp<Fence>::make(readyFence.release());
     }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 644b6ef..772fcd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -565,6 +565,9 @@
     // Display ID is assigned when virtual display is allocated by HWC.
     DisplayDeviceState state;
     state.isSecure = secure;
+    // Set display as protected when marked as secure to ensure no behavior change
+    // TODO (b/314820005): separate as a different arg when creating the display.
+    state.isProtected = secure;
     state.displayName = displayName;
     state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
     mCurrentState.displays.add(token, state);
@@ -721,10 +724,12 @@
         }
 
         readPersistentProperties();
-        mPowerAdvisor->onBootFinished();
         const bool hintSessionEnabled = FlagManager::getInstance().use_adpf_cpu_hint();
         mPowerAdvisor->enablePowerHintSession(hintSessionEnabled);
         const bool hintSessionUsed = mPowerAdvisor->usePowerHintSession();
+        // Ordering is important here, as onBootFinished signals to PowerAdvisor that concurrency
+        // is safe because its variables are initialized.
+        mPowerAdvisor->onBootFinished();
         ALOGD("Power hint is %s",
               hintSessionUsed ? "supported" : (hintSessionEnabled ? "unsupported" : "disabled"));
         if (hintSessionUsed) {
@@ -734,7 +739,7 @@
             if (renderEngineTid.has_value()) {
                 tidList.emplace_back(*renderEngineTid);
             }
-            if (!mPowerAdvisor->startPowerHintSession(tidList)) {
+            if (!mPowerAdvisor->startPowerHintSession(std::move(tidList))) {
                 ALOGW("Cannot start power hint session");
             }
         }
@@ -1177,7 +1182,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 +1195,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(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 +1207,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 +1219,7 @@
 
             mScheduler->setModeChangePending(true);
             break;
-        case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch:
+        case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
             mScheduler->setRenderRate(displayId, mode.fps);
 
             if (displayId == mActiveDisplayId) {
@@ -1227,7 +1231,7 @@
                 dispatchDisplayModeChangeEvent(displayId, mode);
             }
             break;
-        case DisplayDevice::DesiredActiveModeAction::None:
+        case DisplayDevice::DesiredModeAction::None:
             break;
     }
 }
@@ -1287,27 +1291,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 pendingModeOpt = display.getPendingMode();
+    if (!pendingModeOpt) {
         // 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()) {
+    const auto& activeMode = pendingModeOpt->mode;
+
+    if (display.getActiveMode().modePtr->getResolution() != activeMode.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 = activeMode.modePtr.get();
         processDisplayChangesLocked();
 
         // processDisplayChangesLocked will update all necessary components so we're done here.
         return;
     }
 
-    const auto& activeMode = *upcomingModeInfo.modeOpt;
     display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
                                activeMode.fps);
 
@@ -1316,27 +1320,29 @@
         updatePhaseConfiguration(activeMode.fps);
     }
 
-    if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
+    if (pendingModeOpt->emitEvent) {
         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;
-    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);
+void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) {
+    const auto activeModeOpt = display->getDesiredMode();
+    auto activeModePtr = activeModeOpt->mode.modePtr;
+    const auto displayId = activeModePtr->getPhysicalDisplayId();
+    const auto renderFps = activeModeOpt->mode.fps;
+
+    dropModeRequest(display);
+
+    constexpr bool kAllowToEnable = true;
+    mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
     mScheduler->setRenderRate(displayId, renderFps);
 
     if (displayId == mActiveDisplayId) {
@@ -1353,25 +1359,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.
+        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->mode.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,19 +1383,16 @@
               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->mode) {
+            applyActiveMode(display);
             continue;
         }
 
         // Desired active mode was set, it is different than the mode currently in use, however
         // 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);
-        if (!displayModeAllowed) {
-            clearDesiredActiveModeState(display);
+        if (!display->refreshRateSelector().isModeAllowed(desiredModeOpt->mode)) {
+            dropModeRequest(display);
             continue;
         }
 
@@ -1401,13 +1402,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(std::move(*desiredModeOpt), constraints, outTimeline)) {
             continue;
         }
 
@@ -1428,9 +1423,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->mode) {
+            applyActiveMode(display);
         }
     }
 }
@@ -2112,15 +2107,28 @@
     }
 }
 
-void SurfaceFlinger::onComposerHalHotplug(hal::HWDisplayId hwcDisplayId,
-                                          hal::Connection connection) {
-    {
-        std::lock_guard<std::mutex> lock(mHotplugMutex);
-        mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection});
+void SurfaceFlinger::onComposerHalHotplugEvent(hal::HWDisplayId hwcDisplayId,
+                                               DisplayHotplugEvent event) {
+    if (event == DisplayHotplugEvent::CONNECTED || event == DisplayHotplugEvent::DISCONNECTED) {
+        hal::Connection connection = (event == DisplayHotplugEvent::CONNECTED)
+                ? hal::Connection::CONNECTED
+                : hal::Connection::DISCONNECTED;
+        {
+            std::lock_guard<std::mutex> lock(mHotplugMutex);
+            mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection});
+        }
+
+        if (mScheduler) {
+            mScheduler->scheduleConfigure();
+        }
+
+        return;
     }
 
-    if (mScheduler) {
-        mScheduler->scheduleConfigure();
+    if (FlagManager::getInstance().hotplug2()) {
+        ALOGD("SurfaceFlinger got hotplug event=%d", static_cast<int32_t>(event));
+        // TODO(b/311403559): use enum type instead of int
+        mScheduler->onHotplugConnectionError(mAppConnectionHandle, static_cast<int32_t>(event));
     }
 }
 
@@ -2214,7 +2222,7 @@
             continue;
         }
 
-        const bool updateSmallDirty = mScheduler->supportSmallDirtyDetection() &&
+        const bool updateSmallDirty = FlagManager::getInstance().enable_small_area_detection() &&
                 ((snapshot->clientChanges & layer_state_t::eSurfaceDamageRegionChanged) ||
                  snapshot->changes.any(Changes::Geometry));
 
@@ -2487,6 +2495,10 @@
 
     if (pacesetterFrameTarget.isFramePending()) {
         if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
+            if (FlagManager::getInstance().vrr_config()) {
+                mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
+                        pacesetterFrameTarget.expectedPresentTime());
+            }
             scheduleCommit(FrameHint::kNone);
             return false;
         }
@@ -2626,10 +2638,6 @@
                 refreshArgs.outputs.push_back(display->getCompositionDisplay());
             }
         }
-        if (display->getId() == pacesetterId) {
-            // TODO(b/255601557) Update frameInterval per display
-            refreshArgs.frameInterval = display->refreshRateSelector().getActiveMode().fps;
-        }
     }
     mPowerAdvisor->setDisplays(displayIds);
 
@@ -2682,20 +2690,23 @@
         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;
     }
 
+    const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime();
+    // TODO(b/255601557) Update frameInterval per display
+    refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime);
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
-    refreshArgs.expectedPresentTime = pacesetterTarget.expectedPresentTime().ns();
+    refreshArgs.expectedPresentTime = expectedPresentTime.ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
 
     // Store the present time just before calling to the composition engine so we could notify
@@ -3375,6 +3386,7 @@
                       .hwcDisplayId = hwcDisplayId,
                       .activeMode = std::move(activeMode)};
     state.isSecure = true; // All physical displays are currently considered secure.
+    state.isProtected = true;
     state.displayName = std::move(info.name);
 
     mCurrentState.displays.add(token, state);
@@ -3406,6 +3418,7 @@
                                            displayToken, compositionDisplay);
     creationArgs.sequenceId = state.sequenceId;
     creationArgs.isSecure = state.isSecure;
+    creationArgs.isProtected = state.isProtected;
     creationArgs.displaySurface = displaySurface;
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
@@ -3537,6 +3550,7 @@
 
     builder.setPixels(resolution);
     builder.setIsSecure(state.isSecure);
+    builder.setIsProtected(state.isProtected);
     builder.setPowerAdvisor(mPowerAdvisor.get());
     builder.setName(state.displayName);
     auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());
@@ -4016,7 +4030,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 +4059,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);
@@ -4074,7 +4096,10 @@
 
     FeatureFlags features;
 
-    if (sysprop::use_content_detection_for_refresh_rate(false)) {
+    const auto defaultContentDetectionValue =
+            FlagManager::getInstance().enable_fro_dependent_features() &&
+            sysprop::enable_frame_rate_override(true);
+    if (sysprop::use_content_detection_for_refresh_rate(defaultContentDetectionValue)) {
         features |= Feature::kContentDetection;
         if (FlagManager::getInstance().enable_small_area_detection()) {
             features |= Feature::kSmallDirtyContentDetection;
@@ -4337,7 +4362,8 @@
                     int sampleSize = (layer->getChildrenCount() / 100) + 1;
                     layer->traverseChildren([&](Layer* layer) {
                         if (rand() % sampleSize == 0) {
-                            ALOGE("Child Layer: %s", layer->getName().c_str());
+                            ALOGE("Child Layer: %s%s", layer->getName().c_str(),
+                                  (layer->isHandleAlive() ? "handleAlive" : ""));
                         }
                     });
                 }
@@ -4706,7 +4732,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 +5801,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
 
@@ -5804,22 +5830,25 @@
         }
 
         getHwComposer().setPowerMode(displayId, mode);
-        if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) {
+        if (mode != hal::PowerMode::DOZE_SUSPEND &&
+            (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
             const bool enable =
                     mScheduler->getVsyncSchedule(displayId)->getPendingHardwareVsyncState();
             requestHardwareVsync(displayId, enable);
 
-            mScheduler->enableSyntheticVsync(false);
+            if (displayId == mActiveDisplayId) {
+                mScheduler->enableSyntheticVsync(false);
+            }
 
             constexpr bool kAllowToEnable = true;
-            mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, refreshRate);
+            mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, activeMode.get());
         }
 
         mVisibleRegionsDirty = true;
         scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
+        const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND);
         // Turn off the display
-
         if (displayId == mActiveDisplayId) {
             if (const auto display = getActivatableDisplay()) {
                 onActiveDisplayChangedLocked(activeDisplay.get(), *display);
@@ -5833,14 +5862,24 @@
                           strerror(errno));
                 }
 
-                if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
-                    mScheduler->disableHardwareVsync(displayId, true);
+                if (currentModeNotDozeSuspend) {
+                    if (!FlagManager::getInstance().multithreaded_present()) {
+                        mScheduler->disableHardwareVsync(displayId, true);
+                    }
                     mScheduler->enableSyntheticVsync();
                 }
             }
         }
+        if (currentModeNotDozeSuspend && FlagManager::getInstance().multithreaded_present()) {
+            constexpr bool kDisallow = true;
+            mScheduler->disableHardwareVsync(displayId, kDisallow);
+        }
 
-        // Disable VSYNC before turning off the display.
+        // We must disable VSYNC *before* turning off the display. The call to
+        // disableHardwareVsync, above, schedules a task to turn it off after
+        // this method returns. But by that point, the display is OFF, so the
+        // call just updates the pending state, without actually disabling
+        // VSYNC.
         requestHardwareVsync(displayId, false);
         getHwComposer().setPowerMode(displayId, mode);
 
@@ -5849,17 +5888,24 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) {
-            ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
-            mVisibleRegionsDirty = true;
-            scheduleRepaint();
-            mScheduler->enableSyntheticVsync(false);
-            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate);
+        if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND &&
+            (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
+            if (displayId == mActiveDisplayId) {
+                ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
+                mVisibleRegionsDirty = true;
+                scheduleRepaint();
+                mScheduler->enableSyntheticVsync(false);
+            }
+            constexpr bool kAllowToEnable = true;
+            mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, activeMode.get());
         }
     } else if (mode == hal::PowerMode::DOZE_SUSPEND) {
         // Leave display going to doze
+        if (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present()) {
+            constexpr bool kDisallow = true;
+            mScheduler->disableHardwareVsync(displayId, kDisallow);
+        }
         if (displayId == mActiveDisplayId) {
-            mScheduler->disableHardwareVsync(displayId, true);
             mScheduler->enableSyntheticVsync();
         }
         getHwComposer().setPowerMode(displayId, mode);
@@ -7056,7 +7102,7 @@
                 const hal::HWDisplayId hwcId =
                         (Mutex::Autolock(mStateLock), getHwComposer().getPrimaryHwcDisplayId());
 
-                onComposerHalHotplug(hwcId, hal::Connection::CONNECTED);
+                onComposerHalHotplugEvent(hwcId, DisplayHotplugEvent::CONNECTED);
                 return NO_ERROR;
             }
             // Modify the max number of display frames stored within FrameTimeline
@@ -7283,15 +7329,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 display::DisplayModeRequest& request) {
+                    return request.mode.modePtr->getId();
+                });
 
         const bool timerExpired = mKernelIdleTimerEnabled && expired;
 
-        if (display->onKernelTimerChanged(desiredModeId, timerExpired)) {
+        if (display->onKernelTimerChanged(desiredModeIdOpt, timerExpired)) {
             mScheduler->scheduleFrame();
         }
     }));
@@ -7526,6 +7571,12 @@
         return;
     }
 
+    if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
+        return;
+    }
+
     wp<const DisplayDevice> displayWeak;
     ui::LayerStack layerStack;
     ui::Size reqSize(args.width, args.height);
@@ -7559,8 +7610,7 @@
 
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
         return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace,
-                                         args.useIdentityTransform, args.hintForSeamlessTransition,
-                                         args.captureSecureLayers);
+                                         args.hintForSeamlessTransition, args.captureSecureLayers);
     });
 
     GetLayerSnapshotsFunction getLayerSnapshots;
@@ -7613,7 +7663,6 @@
 
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
         return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
-                                         false /* useIdentityTransform */,
                                          args.hintForSeamlessTransition,
                                          false /* captureSecureLayers */);
     });
@@ -7658,8 +7707,11 @@
     std::unordered_set<uint32_t> excludeLayerIds;
     ui::Dataspace dataspace = args.dataspace;
 
-    // Call this before holding mStateLock to avoid any deadlocking.
-    bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
+    if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
+        return;
+    }
 
     {
         Mutex::Autolock lock(mStateLock);
@@ -7671,13 +7723,6 @@
             return;
         }
 
-        if (!canCaptureBlackoutContent &&
-            parent->getDrawingState().flags & layer_state_t::eLayerSecure) {
-            ALOGW("Attempting to capture secure layer: PERMISSION_DENIED");
-            invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
-            return;
-        }
-
         Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getDrawingState());
         if (args.sourceCrop.width() <= 0) {
             crop.left = 0;
@@ -7820,12 +7865,11 @@
                                     })
                                     .get();
     }
-
+    const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
     const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
             GRALLOC_USAGE_HW_TEXTURE |
-            (hasProtectedLayer && allowProtected && supportsProtected
-                     ? GRALLOC_USAGE_PROTECTED
-                     : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+            (isProtected ? GRALLOC_USAGE_PROTECTED
+                         : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
     sp<GraphicBuffer> buffer =
             getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
                                              static_cast<android_pixel_format>(reqPixelFormat),
@@ -7845,18 +7889,17 @@
                                                  renderengine::impl::ExternalTexture::Usage::
                                                          WRITEABLE);
     auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
-                                     false /* regionSampling */, grayscale, captureListener);
+                                     false /* regionSampling */, grayscale, isProtected,
+                                     captureListener);
     fence.get();
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
         RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, const sp<IScreenCaptureListener>& captureListener) {
+        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
-    bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
-
     auto future = mScheduler->schedule(
             [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
@@ -7873,9 +7916,9 @@
 
                 ftl::SharedFuture<FenceResult> renderFuture;
                 renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-                    renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer,
-                                                    canCaptureBlackoutContent, regionSampling,
-                                                    grayscale, captureResults);
+                    renderFuture =
+                            renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
+                                             grayscale, isProtected, captureResults);
                 });
 
                 if (captureListener) {
@@ -7902,9 +7945,8 @@
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
         std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-        bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
-        ScreenCaptureResults& captureResults) {
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
     ATRACE_CALL();
 
     auto layers = getLayerSnapshots();
@@ -7919,14 +7961,6 @@
                 layerFE->mSnapshot->geomLayerTransform.inverse();
     }
 
-    // We allow the system server to take screenshots of secure layers for
-    // use in situations like the Screen-rotation animation and place
-    // the impetus on WindowManager to not persist them.
-    if (captureResults.capturedSecureLayers && !canCaptureBlackoutContent) {
-        ALOGW("FB is protected: PERMISSION_DENIED");
-        return ftl::yield<FenceResult>(base::unexpected(PERMISSION_DENIED)).share();
-    }
-
     auto capturedBuffer = buffer;
 
     auto requestedDataspace = renderArea->getReqDataSpace();
@@ -8007,9 +8041,9 @@
     };
 
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
-                    sdrWhitePointNits, displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(),
-                    layerStack, regionSampling, renderArea = std::move(renderArea),
-                    renderIntent]() -> FenceResult {
+                    sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected,
+                    layerFEs = copyLayerFEs(), layerStack, regionSampling,
+                    renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
         compositionEngine->setRenderEngine(mRenderEngine.get());
@@ -8043,7 +8077,8 @@
                                         .regionSampling = regionSampling,
                                         .treat170mAsSrgb = mTreat170mAsSrgb,
                                         .dimInGammaSpaceForEnhancedScreenshots =
-                                                dimInGammaSpaceForEnhancedScreenshots});
+                                                dimInGammaSpaceForEnhancedScreenshots,
+                                        .isProtected = isProtected});
 
         const float colorSaturation = grayscale ? 0 : 1;
         compositionengine::CompositionRefreshArgs refreshArgs{
@@ -8239,18 +8274,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 +8623,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 +8647,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 +8994,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..6909055 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -106,6 +106,7 @@
 #include <vector>
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
 #include "Client.h"
 
@@ -130,6 +131,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using frontend::TransactionHandler;
 using gui::CaptureArgs;
@@ -629,7 +631,7 @@
     // HWC2::ComposerCallback overrides:
     void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp,
                             std::optional<hal::VsyncPeriodNanos>) override;
-    void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) override;
+    void onComposerHalHotplugEvent(hal::HWDisplayId, DisplayHotplugEvent) override;
     void onComposerHalRefresh(hal::HWDisplayId) override;
     void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
                                                const hal::VsyncPeriodChangeTimeline&) override;
@@ -658,8 +660,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 +688,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 +696,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);
@@ -853,11 +855,11 @@
     ftl::SharedFuture<FenceResult> captureScreenCommon(
             RenderAreaFuture, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, const sp<IScreenCaptureListener>&);
+            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> renderScreenImpl(
             std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
-            bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
+            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+            bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
             REQUIRES(kMainThreadContext);
 
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 9d6d87e..ce8119e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -16,12 +16,10 @@
 
 #undef LOG_TAG
 #define LOG_TAG "TransactionTracing"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
 #include <log/log.h>
 #include <utils/SystemClock.h>
-#include <utils/Trace.h>
 
 #include "Client.h"
 #include "FrontEnd/LayerCreationArgs.h"
@@ -230,7 +228,6 @@
 
 void TransactionTracing::addEntry(const std::vector<CommittedUpdates>& committedUpdates,
                                   const std::vector<uint32_t>& destroyedLayers) {
-    ATRACE_CALL();
     std::scoped_lock lock(mTraceLock);
     std::vector<std::string> removedEntries;
     perfetto::protos::TransactionTraceEntry entryProto;
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index e2a1498..adb4974 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -121,6 +121,11 @@
     DUMP_READ_ONLY_FLAG(hdcp_level_hal);
     DUMP_READ_ONLY_FLAG(multithreaded_present);
     DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
+    DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
+    DUMP_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved);
+    DUMP_READ_ONLY_FLAG(enable_fro_dependent_features);
+    DUMP_READ_ONLY_FLAG(display_protected);
+    DUMP_READ_ONLY_FLAG(fp16_client_target);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -190,6 +195,12 @@
 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, "")
+FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
+FLAG_MANAGER_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved,
+                            "debug.sf.cache_source_crop_only_moved")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "")
+FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
+FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 
 /// 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 9aabbb9..cdab461 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -60,6 +60,11 @@
     bool hdcp_level_hal() const;
     bool multithreaded_present() const;
     bool add_sf_skipped_frames_to_trace() const;
+    bool use_known_refresh_rate_for_fps_consistency() const;
+    bool cache_if_source_crop_layer_only_moved() const;
+    bool enable_fro_dependent_features() const;
+    bool display_protected() const;
+    bool fp16_client_target() 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_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index afb5f5c..68237c8 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -286,7 +286,7 @@
 
     composer.setClientTarget(display, mFdp.ConsumeIntegral<uint32_t>(), sp<GraphicBuffer>(),
                              mFdp.ConsumeIntegral<int32_t>(), mFdp.PickValueInArray(kDataspaces),
-                             {});
+                             {}, mFdp.ConsumeFloatingPoint<float>());
 
     composer.setColorMode(display, mFdp.PickValueInArray(kColormodes),
                           mFdp.PickValueInArray(kRenderIntents));
@@ -494,7 +494,7 @@
     surface->beginFrame(mFdp.ConsumeBool());
 
     surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
-    surface->advanceFrame();
+    surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
     surface->onFrameCommitted();
     String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
     surface->dumpAsString(result);
@@ -530,7 +530,7 @@
     surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
     surface->resizeBuffers(getFuzzedSize());
     surface->getClientTargetAcquireFence();
-    surface->advanceFrame();
+    surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
     surface->onFrameCommitted();
     String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
     surface->dumpAsString(result);
@@ -561,7 +561,8 @@
     getDeviceCompositionChanges(halDisplayID);
 
     mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
-                         sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces));
+                         sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces),
+                         mFdp.ConsumeFloatingPoint<float>());
 
     mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now());
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
index 1a951b3..b2dc20e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -41,6 +41,7 @@
 
 namespace android::hardware::graphics::composer::hal {
 
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
@@ -52,7 +53,9 @@
           : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
 
     Return<void> onHotplug(HWDisplayId display, Connection connection) override {
-        mCallback->onComposerHalHotplug(display, connection);
+        const auto event = connection == Connection::CONNECTED ? DisplayHotplugEvent::CONNECTED
+                                                               : DisplayHotplugEvent::DISCONNECTED;
+        mCallback->onComposerHalHotplugEvent(display, event);
         return Void();
     }
 
@@ -94,7 +97,7 @@
 
 struct TestHWC2ComposerCallback : public HWC2::ComposerCallback {
     virtual ~TestHWC2ComposerCallback() = default;
-    void onComposerHalHotplug(HWDisplayId, Connection){};
+    void onComposerHalHotplugEvent(HWDisplayId, DisplayHotplugEvent) {}
     void onComposerHalRefresh(HWDisplayId) {}
     void onComposerHalVsync(HWDisplayId, int64_t, std::optional<VsyncPeriodNanos>) {}
     void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {}
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 71c59b2..88737c1 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -84,3 +84,42 @@
   description: "enable refresh rate indicator on the external display"
   bug: "301647974"
 }
+
+flag {
+  name: "use_known_refresh_rate_for_fps_consistency"
+  namespace: "core_graphics"
+  description: "Whether to use the closest known refresh rate to determine the fps consistency."
+  bug: "299201319"
+}
+
+flag {
+  name: "cache_if_source_crop_layer_only_moved"
+  namespace: "core_graphics"
+  description: "do not flatten layers if source crop is only moved"
+  bug: "305718400"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "enable_fro_dependent_features"
+  namespace: "core_graphics"
+  description: "enable frame rate override dependent features by default"
+  bug: "314217419"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "display_protected"
+  namespace: "core_graphics"
+  description: "Introduce protected displays to specify whether they should render protected content"
+  bug: "301647974"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "fp16_client_target"
+  namespace: "core_graphics"
+  description: "Controls whether we render to fp16 client targets"
+  bug: "236745178"
+  is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 0a951d4..aeff5a5 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -56,60 +56,44 @@
         ASSERT_EQ(res, NO_ERROR);
     }
 
-    void testSetAllowGroupSwitching(bool allowGroupSwitching);
+    void testSetDesiredDisplayModeSpecs(bool allowGroupSwitching = false) {
+        ui::DynamicDisplayInfo info;
+        status_t res =
+                SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast<int64_t>(mDisplayId),
+                                                                   &info);
+        const auto& modes = info.supportedDisplayModes;
+        ASSERT_EQ(res, NO_ERROR);
+        ASSERT_GT(modes.size(), 0);
+        for (const auto& mode : modes) {
+            gui::DisplayModeSpecs setSpecs;
+            setSpecs.defaultMode = mode.id;
+            setSpecs.allowGroupSwitching = allowGroupSwitching;
+            setSpecs.primaryRanges.physical.min = mode.peakRefreshRate;
+            setSpecs.primaryRanges.physical.max = mode.peakRefreshRate;
+            setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
+            setSpecs.appRequestRanges = setSpecs.primaryRanges;
+
+            res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
+            ASSERT_EQ(res, NO_ERROR);
+            gui::DisplayModeSpecs getSpecs;
+            res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
+            ASSERT_EQ(res, NO_ERROR);
+            ASSERT_EQ(setSpecs, getSpecs);
+        }
+    }
 
     sp<IBinder> mDisplayToken;
     uint64_t mDisplayId;
 };
 
 TEST_F(RefreshRateRangeTest, setAllConfigs) {
-    ui::DynamicDisplayInfo info;
-    status_t res =
-            SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast<int64_t>(mDisplayId),
-                                                               &info);
-    const auto& modes = info.supportedDisplayModes;
-    ASSERT_EQ(res, NO_ERROR);
-    ASSERT_GT(modes.size(), 0);
-
-    gui::DisplayModeSpecs setSpecs;
-    setSpecs.allowGroupSwitching = false;
-    for (size_t i = 0; i < modes.size(); i++) {
-        setSpecs.defaultMode = modes[i].id;
-        setSpecs.primaryRanges.physical.min = modes[i].peakRefreshRate;
-        setSpecs.primaryRanges.physical.max = modes[i].peakRefreshRate;
-        setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
-        setSpecs.appRequestRanges = setSpecs.primaryRanges;
-        res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
-        ASSERT_EQ(res, NO_ERROR);
-
-        gui::DisplayModeSpecs getSpecs;
-        res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
-        ASSERT_EQ(res, NO_ERROR);
-        ASSERT_EQ(setSpecs, getSpecs);
-    }
-}
-
-void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) {
-    gui::DisplayModeSpecs setSpecs;
-    setSpecs.defaultMode = 0;
-    setSpecs.allowGroupSwitching = allowGroupSwitching;
-    setSpecs.primaryRanges.physical.min = 0;
-    setSpecs.primaryRanges.physical.max = 90;
-    setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
-    setSpecs.appRequestRanges = setSpecs.primaryRanges;
-
-    status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
-    ASSERT_EQ(res, NO_ERROR);
-    gui::DisplayModeSpecs getSpecs;
-    res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
-    ASSERT_EQ(res, NO_ERROR);
-    ASSERT_EQ(setSpecs, getSpecs);
+    testSetDesiredDisplayModeSpecs();
 }
 
 TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) {
-    testSetAllowGroupSwitching(true);
-    testSetAllowGroupSwitching(false);
-    testSetAllowGroupSwitching(true);
+    testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/true);
+    testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/false);
+    testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/true);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp
index 2181370..15a98df 100644
--- a/services/surfaceflinger/tests/LayerState_test.cpp
+++ b/services/surfaceflinger/tests/LayerState_test.cpp
@@ -38,7 +38,6 @@
     args.displayToken = sp<BBinder>::make();
     args.width = 10;
     args.height = 20;
-    args.useIdentityTransform = true;
     args.grayscale = true;
 
     Parcel p;
@@ -56,7 +55,6 @@
     ASSERT_EQ(args.displayToken, args2.displayToken);
     ASSERT_EQ(args.width, args2.width);
     ASSERT_EQ(args.height, args2.height);
-    ASSERT_EQ(args.useIdentityTransform, args2.useIdentityTransform);
     ASSERT_EQ(args.grayscale, args2.grayscale);
 }
 
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 79864e0..18262f6 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -99,25 +99,42 @@
         ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     }
 
-    UIDFaker f(AID_SYSTEM);
-
-    // By default the system can capture screenshots with secure layers but they
-    // will be blacked out
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
-
     {
-        SCOPED_TRACE("as system");
-        auto shot = screenshot();
-        shot->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+        UIDFaker f(AID_SYSTEM);
+
+        // By default the system can capture screenshots with secure layers but they
+        // will be blacked out
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+
+        {
+            SCOPED_TRACE("as system");
+            auto shot = screenshot();
+            shot->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+        }
+
+        mCaptureArgs.captureSecureLayers = true;
+        // AID_SYSTEM is allowed to capture secure content.
+        ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+        ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+        ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+        sc.expectColor(Rect(0, 0, 32, 32), Color::RED);
     }
 
-    // Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
-    // to receive them...we are expected to take care with the results.
-    mCaptureArgs.captureSecureLayers = true;
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
-    ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
-    ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
-    sc.expectColor(Rect(0, 0, 32, 32), Color::RED);
+    {
+        // Attempt secure screenshot from shell since it doesn't have CAPTURE_BLACKOUT_CONTENT
+        // permission, but is allowed normal screenshots.
+        UIDFaker faker(AID_SHELL);
+        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+    }
+
+    // Remove flag secure from the layer.
+    Transaction().setFlags(layer, 0, layer_state_t::eLayerSecure).apply(true);
+    {
+        // Assert that screenshot fails without CAPTURE_BLACKOUT_CONTENT when requesting
+        // captureSecureLayers even if there are no actual secure layers on screen.
+        UIDFaker faker(AID_SHELL);
+        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+    }
 }
 
 TEST_F(ScreenCaptureTest, CaptureChildSetParentFlagsSecureEUidSystem) {
@@ -154,6 +171,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/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index ee967979..beb2147 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -194,7 +194,6 @@
     LayerCase::setupForScreenCapture(this);
 
     const Rect sourceCrop(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
-    constexpr bool forSystem = true;
     constexpr bool regionSampling = false;
 
     auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
@@ -216,7 +215,7 @@
                                                                       usage);
 
     auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
-                                            mCaptureScreenBuffer, forSystem, regionSampling);
+                                            mCaptureScreenBuffer, regionSampling);
     ASSERT_TRUE(future.valid());
     const auto fenceResult = future.get();
 
@@ -316,7 +315,7 @@
         EXPECT_CALL(*test->mComposer, getReleaseFences(HWC_DISPLAY, _, _)).Times(1);
 
         EXPECT_CALL(*test->mDisplaySurface, onFrameCommitted()).Times(1);
-        EXPECT_CALL(*test->mDisplaySurface, advanceFrame()).Times(1);
+        EXPECT_CALL(*test->mDisplaySurface, advanceFrame(_)).Times(1);
 
         Case::CompositionType::setupHwcSetCallExpectations(test);
         Case::CompositionType::setupHwcGetCallExpectations(test);
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index 2d87ddd..c463a92 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -23,16 +23,20 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt)                               \
+    ASSERT_TRUE(requestOpt);                                                            \
+    EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
+    EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
+
 namespace android {
 namespace {
 
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using DisplayModeRequest = display::DisplayModeRequest;
 
 class InitiateModeChangeTest : public DisplayTransactionTest {
 public:
-    using Action = DisplayDevice::DesiredActiveModeAction;
-    using Event = scheduler::DisplayModeEvent;
-
+    using Action = DisplayDevice::DesiredModeAction;
     void SetUp() override {
         injectFakeBufferQueueFactory();
         injectFakeNativeWindowSurfaceFactory();
@@ -43,7 +47,8 @@
         PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
         PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
 
-        mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                           DisplayHotplugEvent::CONNECTED);
         mFlinger.configureAndCommit();
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
@@ -64,110 +69,83 @@
             ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
     static inline const ftl::NonNull<DisplayModePtr> kMode120 =
             ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
+
+    static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
+    static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
 };
 
-TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) {
-    EXPECT_EQ(Action::None,
-              mDisplay->setDesiredActiveMode(
-                      {scheduler::FrameRateMode{60_Hz, kMode60}, Event::None}));
-    EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) {
+    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode60)));
+    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(DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
 
-    // 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);
+    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
 }
 
-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(DisplayModeRequest(kDesiredMode90)));
+    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(DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
 
-    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_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
 
-    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(DisplayModeRequest(kDesiredMode30)));
+    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(DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
 
-    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_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
 
-    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);
+    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
+    ASSERT_TRUE(mDisplay->getDesiredMode());
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
 
-    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
-    EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
 
-    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_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getPendingMode());
 
-    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/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
index 20ea0c0..5c742d7 100644
--- a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -49,6 +49,7 @@
     const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
     const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
     const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_DEFAULT = FrameRate(Fps(), FrameRateCompatibility::Default);
     const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
 
     FrameRateSelectionStrategyTest();
@@ -102,7 +103,7 @@
               layer->getDrawingState().frameRateSelectionStrategy);
 }
 
-TEST_P(FrameRateSelectionStrategyTest, SetChildAndGetParent) {
+TEST_P(FrameRateSelectionStrategyTest, SetChildOverrideChildren) {
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
@@ -116,17 +117,17 @@
     child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
     commitTransaction();
     EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
               parent->getDrawingState().frameRateSelectionStrategy);
     EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
               child1->getDrawingState().frameRateSelectionStrategy);
     EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
     EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
               child2->getDrawingState().frameRateSelectionStrategy);
 }
 
-TEST_P(FrameRateSelectionStrategyTest, SetParentAndGet) {
+TEST_P(FrameRateSelectionStrategyTest, SetParentOverrideChildren) {
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
@@ -137,7 +138,6 @@
     addChild(layer2, layer3);
 
     layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
-    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
     layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
     layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
     layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
@@ -151,20 +151,72 @@
     EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
               layer2->getDrawingState().frameRateSelectionStrategy);
     EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
               layer3->getDrawingState().frameRateSelectionStrategy);
 
-    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Propagate);
     commitTransaction();
 
     EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
               layer1->getDrawingState().frameRateSelectionStrategy);
     EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
     EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
               layer2->getDrawingState().frameRateSelectionStrategy);
     EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, OverrideChildrenAndSelf) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(layer1, layer2);
+    addChild(layer2, layer3);
+
+    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+    layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
     EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_DEFAULT, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+
+    layer1->setFrameRate(FRAME_RATE_DEFAULT.vote);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_TREE, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
               layer3->getDrawingState().frameRateSelectionStrategy);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 6d87717..ddc3967 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "FlagUtils.h"
+#include <common/test/FlagUtils.h>
 #include "com_android_graphics_surfaceflinger_flags.h"
 #include "gmock/gmock-spec-builders.h"
 #include "mock/MockTimeStats.h"
@@ -41,6 +41,7 @@
 using ProtoFrameEnd = perfetto::protos::FrameTimelineEvent_FrameEnd;
 using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
 using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
+using ProtoJankSeverityType = perfetto::protos::FrameTimelineEvent_JankSeverityType;
 using ProtoPredictionType = perfetto::protos::FrameTimelineEvent_PredictionType;
 
 namespace android::frametimeline {
@@ -335,7 +336,9 @@
     EXPECT_EQ(presentedSurfaceFrame1.getActuals().presentTime, 42);
     EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42);
     EXPECT_NE(surfaceFrame1->getJankType(), std::nullopt);
+    EXPECT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
     EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
+    EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
 }
 
 TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
@@ -493,8 +496,10 @@
     auto displayFrame0 = getDisplayFrame(0);
     EXPECT_EQ(displayFrame0->getActuals().presentTime, 59);
     EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+    EXPECT_EQ(displayFrame0->getJankSeverityType(), JankSeverityType::Unknown);
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, -1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Unknown);
 }
 
 // Tests related to TimeStats
@@ -604,6 +609,7 @@
     presentFence1->signalForTest(90);
     mFrameTimeline->setSfPresent(56, presentFence1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
@@ -633,6 +639,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
@@ -662,6 +669,7 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
@@ -691,6 +699,7 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
@@ -721,6 +730,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
@@ -752,6 +762,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
@@ -788,12 +799,14 @@
 
     auto displayFrame = getDisplayFrame(0);
     EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
     EXPECT_EQ(displayFrame->getFrameStartMetadata(), FrameStartMetadata::UnknownStart);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
 
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 90);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown | JankType::AppDeadlineMissed);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 /*
@@ -920,7 +933,8 @@
 
 ProtoActualDisplayFrameStart createProtoActualDisplayFrameStart(
         int64_t cookie, int64_t token, pid_t pid, ProtoPresentType presentType, bool onTimeFinish,
-        bool gpuComposition, ProtoJankType jankType, ProtoPredictionType predictionType) {
+        bool gpuComposition, ProtoJankType jankType, ProtoJankSeverityType jankSeverityType,
+        ProtoPredictionType predictionType) {
     ProtoActualDisplayFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -929,6 +943,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_jank_severity_type(jankSeverityType);
     proto.set_prediction_type(predictionType);
     return proto;
 }
@@ -949,7 +964,8 @@
 ProtoActualSurfaceFrameStart createProtoActualSurfaceFrameStart(
         int64_t cookie, int64_t token, int64_t displayFrameToken, pid_t pid, std::string layerName,
         ProtoPresentType presentType, bool onTimeFinish, bool gpuComposition,
-        ProtoJankType jankType, ProtoPredictionType predictionType, bool isBuffer) {
+        ProtoJankType jankType, ProtoJankSeverityType jankSeverityType,
+        ProtoPredictionType predictionType, bool isBuffer) {
     ProtoActualSurfaceFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -960,6 +976,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_jank_severity_type(jankSeverityType);
     proto.set_prediction_type(predictionType);
     proto.set_is_buffer(isBuffer);
     return proto;
@@ -1002,6 +1019,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_jank_severity_type());
+    EXPECT_EQ(received.jank_severity_type(), source.jank_severity_type());
     ASSERT_TRUE(received.has_prediction_type());
     EXPECT_EQ(received.prediction_type(), source.prediction_type());
 }
@@ -1049,6 +1068,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_jank_severity_type());
+    EXPECT_EQ(received.jank_severity_type(), source.jank_severity_type());
     ASSERT_TRUE(received.has_prediction_type());
     EXPECT_EQ(received.prediction_type(), source.prediction_type());
     ASSERT_TRUE(received.has_is_buffer());
@@ -1116,6 +1137,7 @@
             createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_DROPPED, true, false,
                                                FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_NONE,
                                                FrameTimelineEvent::PREDICTION_VALID);
     auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9);
 
@@ -1176,6 +1198,7 @@
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
                                                FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::SEVERITY_NONE,
                                                FrameTimelineEvent::PREDICTION_VALID);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 2);
 
@@ -1255,6 +1278,7 @@
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
                                                false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::SEVERITY_UNKNOWN,
                                                FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
@@ -1330,6 +1354,7 @@
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_DROPPED, true, false,
                                                FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_UNKNOWN,
                                                FrameTimelineEvent::PREDICTION_VALID, true);
     auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2);
 
@@ -1342,6 +1367,7 @@
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
                                                FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::SEVERITY_NONE,
                                                FrameTimelineEvent::PREDICTION_VALID, true);
     auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
 
@@ -1488,6 +1514,7 @@
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
                                                false, FrameTimelineEvent::JANK_APP_DEADLINE_MISSED,
+                                               FrameTimelineEvent::SEVERITY_UNKNOWN,
                                                FrameTimelineEvent::PREDICTION_EXPIRED, true);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
@@ -1565,6 +1592,7 @@
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_DROPPED, false, false,
                                                FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_UNKNOWN,
                                                FrameTimelineEvent::PREDICTION_EXPIRED, true);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
@@ -1643,6 +1671,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
@@ -1669,6 +1698,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     auto displayFrame2 = getDisplayFrame(1);
@@ -1682,6 +1712,7 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
@@ -1708,6 +1739,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::DisplayHAL);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     auto displayFrame2 = getDisplayFrame(1);
@@ -1722,6 +1754,7 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent) {
@@ -1744,6 +1777,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent) {
@@ -1789,6 +1823,7 @@
     EXPECT_EQ(displayFrame0->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame0->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame0->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+    EXPECT_EQ(displayFrame0->getJankSeverityType(), JankSeverityType::Full);
 
     // case 3 - cpu time = 86 - 82 = 4, vsync period = 30
     mFrameTimeline->setSfWakeUp(sfToken3, 106, RR_30, RR_30);
@@ -1803,6 +1838,7 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Full);
 
     // case 4 - cpu time = 86 - 82 = 4, vsync period = 30
     mFrameTimeline->setSfWakeUp(sfToken4, 120, RR_30, RR_30);
@@ -1817,6 +1853,7 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerStuffing);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
 
     addEmptyDisplayFrame();
 
@@ -1825,6 +1862,7 @@
     EXPECT_EQ(displayFrame3->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame3->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame3->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+    EXPECT_EQ(displayFrame3->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresent) {
@@ -1877,12 +1915,14 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Partial);
 
     actuals1 = presentedSurfaceFrame1.getActuals();
     EXPECT_EQ(actuals1.presentTime, 30);
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     presentFence2->signalForTest(65);
@@ -1905,12 +1945,14 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Partial);
 
     actuals2 = presentedSurfaceFrame2.getActuals();
     EXPECT_EQ(actuals2.presentTime, 65);
     EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent) {
@@ -1963,12 +2005,14 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::DisplayHAL);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Partial);
 
     actuals1 = presentedSurfaceFrame1.getActuals();
     EXPECT_EQ(actuals1.presentTime, 50);
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::DisplayHAL);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     presentFence2->signalForTest(86);
@@ -1991,12 +2035,14 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
 
     actuals2 = presentedSurfaceFrame2.getActuals();
     EXPECT_EQ(actuals2.presentTime, 86);
     EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent) {
@@ -2033,12 +2079,14 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
 
     actuals1 = presentedSurfaceFrame1.getActuals();
     EXPECT_EQ(actuals1.presentTime, 50);
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::Unknown);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) {
@@ -2095,12 +2143,14 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
 
     actuals1 = presentedSurfaceFrame1.getActuals();
     EXPECT_EQ(actuals1.presentTime, 40);
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     presentFence2->signalForTest(60);
@@ -2115,6 +2165,7 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Partial);
 
     actuals2 = presentedSurfaceFrame2.getActuals();
     EXPECT_EQ(actuals2.presentTime, 60);
@@ -2122,6 +2173,7 @@
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
               JankType::SurfaceFlingerCpuDeadlineMissed | JankType::AppDeadlineMissed);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadlineMissed) {
@@ -2181,10 +2233,12 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
 
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Full);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
@@ -2201,11 +2255,13 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::None);
 
     EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
               JankType::AppDeadlineMissed | JankType::BufferStuffing);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffing) {
@@ -2266,10 +2322,12 @@
     EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
 
     EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Full);
 
     // Fences for the second frame haven't been flushed yet, so it should be 0
     EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
@@ -2286,10 +2344,12 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::None);
 
     EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::BufferStuffing);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent_GpuAndCpuMiss) {
@@ -2317,6 +2377,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+    EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Full);
 
     // Case 2: No GPU fence so it will not use GPU composition.
     mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
@@ -2334,6 +2395,7 @@
     EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
     EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+    EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_presentFenceError) {
@@ -2364,6 +2426,7 @@
         EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
         EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
         EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+        EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
     }
     {
         auto displayFrame = getDisplayFrame(1);
@@ -2371,6 +2434,7 @@
         EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
         EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
         EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+        EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
     }
     {
         auto displayFrame = getDisplayFrame(2);
@@ -2378,6 +2442,7 @@
         EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
         EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
         EXPECT_EQ(displayFrame->getJankType(), JankType::None);
+        EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index f1e841b..d3ce4f2 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"
 
@@ -57,6 +57,7 @@
 
 using Hwc2::Config;
 
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using hal::IComposerClient;
 using ::testing::_;
@@ -555,7 +556,8 @@
 }
 
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
-    MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
+    MOCK_METHOD(void, onComposerHalHotplugEvent, (hal::HWDisplayId, DisplayHotplugEvent),
+                (override));
     MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
     MOCK_METHOD3(onComposerHalVsync,
                  void(hal::HWDisplayId, int64_t timestamp, std::optional<hal::VsyncPeriodNanos>));
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 48f8923..7e9abce 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -478,6 +478,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setDropInputMode(uint32_t id, gui::DropInputMode dropInputMode) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eDropInputModeChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.dropInputMode = dropInputMode;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     LayerLifecycleManager mLifecycleManager;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index befef48..787fa1c 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"
@@ -768,6 +768,7 @@
 }
 
 TEST_F(LayerHistoryIntegrationTest, heuristicLayerNotOscillating) {
+    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, false);
     auto layer = createLegacyAndFrontedEndLayer(1);
 
     nsecs_t time = systemTime();
@@ -779,6 +780,20 @@
     recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
 }
 
+TEST_F(LayerHistoryIntegrationTest, heuristicLayerNotOscillating_useKnownRefreshRate) {
+    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, true);
+    auto layer = createLegacyAndFrontedEndLayer(1);
+
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
 TEST_F(LayerHistoryIntegrationTest, smallDirtyLayer) {
     auto layer = createLegacyAndFrontedEndLayer(1);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 1adf14f..b88ef56 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"
@@ -1169,6 +1169,8 @@
 }
 
 TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
+    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, false);
+
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
@@ -1182,6 +1184,23 @@
     recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
 }
 
+TEST_F(LayerHistoryTest, heuristicLayerNotOscillating_useKnownRefreshRate) {
+    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, true);
+
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
 TEST_F(LayerHistoryTest, smallDirtyLayer) {
     auto layer = createLayer();
 
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..50cd784 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -814,24 +814,24 @@
     EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
               scheduler::FrameRateCompatibility::NoVote);
     EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     EXPECT_FALSE(getSnapshot({.id = 121})->frameRate.vote.rate.isValid());
     EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
               scheduler::FrameRateCompatibility::Default);
     EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
     EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
 }
 
@@ -904,12 +904,12 @@
               scheduler::FrameRateCompatibility::NoVote);
     EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::Default);
     EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
     EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::Default);
     EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
               scheduler::FrameRateCompatibility::Default);
@@ -917,17 +917,105 @@
 
     EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
     EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Default);
     EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
-              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Default);
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
 }
 
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithOverrideChildrenAndSelf) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11 (frame rate set to 11.f with strategy Self)
+    // │   │   └── 111 (frame rate is not inherited)
+    // │   ├── 12 (frame rate set to 244.f)
+    // │   │   ├── 121
+    // │   │   └── 122 (strategy OverrideChildren and inherits frame rate 244.f)
+    // │   │       └── 1221 (frame rate set to 123.f but should be overridden by layer 122)
+    // │   └── 13
+    // └── 2
+    setFrameRate(11, 11.f, 0, 0);
+    setFrameRateSelectionStrategy(11, 2 /* Self */);
+    setFrameRate(12, 244.f, 0, 0);
+    setFrameRateSelectionStrategy(122, 1 /* OverrideChildren */);
+    setFrameRate(1221, 123.f, 0, 0);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 11.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 11 does does not propagate its framerate to 111.
+    EXPECT_FALSE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+    EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // ROOT
+    // ├── 1 (frame rate set to 1.f with strategy OverrideChildren)
+    // │   ├── 11 (frame rate set to 11.f with strategy Self, but overridden by 1)
+    // │   │   └── 111 (frame rate inherited from 11 due to override from 1)
+    // â‹®   â‹®
+    setFrameRate(1, 1.f, 0, 0);
+    setFrameRateSelectionStrategy(1, 1 /* OverrideChildren */);
+    setFrameRate(11, 11.f, 0, 0);
+    setFrameRateSelectionStrategy(11, 2 /* Self */);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.rate.getValue(), 1.f);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 1.f);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 11 does does not propagate its framerate to 111.
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 1.f);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
 TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
     setRoundedCorners(1, 42.f);
     setRoundedCorners(2, 42.f);
@@ -1055,4 +1143,44 @@
     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);
+}
+
+// b/314350323
+TEST_F(LayerSnapshotTest, propagateDropInputMode) {
+    setDropInputMode(1, gui::DropInputMode::ALL);
+    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_EQ(getSnapshot(1)->dropInputMode, gui::DropInputMode::ALL);
+    // Ensure child also has the correct drop input mode regardless of whether either layer has
+    // an input channel
+    EXPECT_EQ(getSnapshot(11)->dropInputMode, gui::DropInputMode::ALL);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 85f66f4..9c66a97 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -24,6 +24,7 @@
 #include <powermanager/PowerHalWrapper.h>
 #include <ui/DisplayId.h>
 #include <chrono>
+#include <future>
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockIPowerHintSession.h"
 #include "mock/DisplayHardware/MockPowerHalController.h"
@@ -40,11 +41,14 @@
 class PowerAdvisorTest : public testing::Test {
 public:
     void SetUp() override;
-    void startPowerHintSession();
+    void startPowerHintSession(bool returnValidSession = true);
     void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod);
     void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime);
     Duration getFenceWaitDelayDuration(bool skipValidate);
     Duration getErrorMargin();
+    void setTimingTestingMode(bool testinMode);
+    void allowReportActualToAcquireMutex();
+    bool sessionExists();
 
 protected:
     TestableSurfaceFlinger mFlinger;
@@ -53,6 +57,11 @@
     std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
 };
 
+bool PowerAdvisorTest::sessionExists() {
+    std::scoped_lock lock(mPowerAdvisor->mHintSessionMutex);
+    return mPowerAdvisor->mHintSession != nullptr;
+}
+
 void PowerAdvisorTest::SetUp() {
     mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
     mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
@@ -62,14 +71,20 @@
             .WillByDefault(Return(HalResult<int64_t>::fromStatus(binder::Status::ok(), 16000)));
 }
 
-void PowerAdvisorTest::startPowerHintSession() {
-    const std::vector<int32_t> threadIds = {1, 2, 3};
+void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
     mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
-    ON_CALL(*mMockPowerHalController, createHintSession)
-            .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                          fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+    if (returnValidSession) {
+        ON_CALL(*mMockPowerHalController, createHintSession)
+                .WillByDefault(
+                        Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+                                       fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+    } else {
+        ON_CALL(*mMockPowerHalController, createHintSession)
+                .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+                                              fromStatus(binder::Status::ok(), nullptr)));
+    }
     mPowerAdvisor->enablePowerHintSession(true);
-    mPowerAdvisor->startPowerHintSession(threadIds);
+    mPowerAdvisor->startPowerHintSession({1, 2, 3});
     ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
             .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
 }
@@ -86,6 +101,14 @@
     mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod);
 }
 
+void PowerAdvisorTest::setTimingTestingMode(bool testingMode) {
+    mPowerAdvisor->mTimingTestingMode = testingMode;
+}
+
+void PowerAdvisorTest::allowReportActualToAcquireMutex() {
+    mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
+}
+
 Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
     return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
                          : PowerAdvisor::kFenceWaitStartDelayValidated);
@@ -221,5 +244,131 @@
     mPowerAdvisor->reportActualWorkDuration();
 }
 
+TEST_F(PowerAdvisorTest, hintSessionValidWhenNullFromPowerHAL) {
+    mPowerAdvisor->onBootFinished();
+
+    startPowerHintSession(false);
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+
+    // 60hz
+    const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+    const Duration presentDuration = 5ms;
+    const Duration postCompDuration = 1ms;
+
+    TimePoint startTime{100ns};
+
+    // advisor only starts on frame 2 so do an initial no-op frame
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+
+    // increment the frame
+    startTime += vsyncPeriod;
+
+    const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+    EXPECT_CALL(*mMockPowerHintSession,
+                reportActualWorkDuration(ElementsAre(
+                        Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+            .Times(0);
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->reportActualWorkDuration();
+}
+
+TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
+    EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+    mPowerAdvisor->startPowerHintSession({1, 2, 3});
+}
+
+TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) {
+    // notifyDisplayUpdateImminentAndCpuReset or notifyCpuLoadUp gets called in background
+    // reportActual gets called during callback and sees true session, passes ensure
+    // first notify finishes, setting value to true. Another async method gets called, acquires the
+    // lock between reportactual finishing ensure and acquiring the lock itself, and sets session to
+    // nullptr. reportActual acquires the lock, and the session is now null, so it does nullptr
+    // deref
+
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+
+    // --- fake a bunch of timing data
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+    // 60hz
+    const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+    const Duration presentDuration = 5ms;
+    const Duration postCompDuration = 1ms;
+    TimePoint startTime{100ns};
+    // advisor only starts on frame 2 so do an initial no-op frame
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+    // increment the frame
+    startTime += vsyncPeriod;
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    // --- Done faking timing data
+
+    setTimingTestingMode(true);
+    std::promise<bool> letSendHintFinish;
+
+    ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] {
+        letSendHintFinish.get_future().wait();
+        return ndk::ScopedAStatus::fromExceptionCode(-127);
+    });
+
+    ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] {
+        return ndk::ScopedAStatus::fromExceptionCode(-127);
+    });
+
+    ON_CALL(*mMockPowerHalController, createHintSession)
+            .WillByDefault(Return(
+                    HalResult<std::shared_ptr<IPowerHintSession>>::
+                            fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
+
+    // First background call, to notice the session is down
+    auto firstHint = std::async(std::launch::async, [this] {
+        mPowerAdvisor->notifyCpuLoadUp();
+        return true;
+    });
+    std::this_thread::sleep_for(10ms);
+
+    // Call reportActual while callback is resolving to try and sneak past ensure
+    auto reportActual =
+            std::async(std::launch::async, [this] { mPowerAdvisor->reportActualWorkDuration(); });
+
+    std::this_thread::sleep_for(10ms);
+    // Let the first call finish
+    letSendHintFinish.set_value(true);
+    letSendHintFinish = std::promise<bool>{};
+    firstHint.wait();
+
+    // Do the second notify call, to ensure the session is nullptr
+    auto secondHint = std::async(std::launch::async, [this] {
+        mPowerAdvisor->notifyCpuLoadUp();
+        return true;
+    });
+    letSendHintFinish.set_value(true);
+    secondHint.wait();
+    // Let report finish, potentially dereferencing
+    allowReportActualToAcquireMutex();
+    reportActual.wait();
+    EXPECT_EQ(sessionExists(), false);
+}
+
 } // namespace
 } // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a00e886..0cacf81 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"
@@ -1149,6 +1150,75 @@
     EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitGte) {
+    auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 25_Hz;
+    lr1.name = "25Hz ExplicitGte";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 91_Hz;
+    lr1.name = "91Hz ExplicitGte";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.name = "90Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+    lr1.vote = LayerVoteType::ExplicitGte;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitGte";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.name = "120Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+}
+
 TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
@@ -1543,6 +1613,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 +1681,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 +1696,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 +3558,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 +3578,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 +3636,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 +3660,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 +3690,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..e515895 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/test/FlagUtils.h>
 #include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -23,6 +24,7 @@
 
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateSelector.h"
+#include "Scheduler/VSyncPredictor.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
@@ -32,11 +34,15 @@
 
 #include <FrontEnd/LayerHierarchy.h>
 
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include "FpsOps.h"
 
+using namespace com::android::graphics::surfaceflinger;
+
 namespace android::scheduler {
 
 using android::mock::createDisplayMode;
+using android::mock::createVrrDisplayMode;
 
 using testing::_;
 using testing::Return;
@@ -457,26 +463,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 +517,174 @@
         }
 
         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);
+}
+
+TEST_F(SchedulerTest, nextFrameIntervalTest) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    static constexpr size_t kHistorySize = 10;
+    static constexpr size_t kMinimumSamplesForPrediction = 6;
+    static constexpr size_t kOutlierTolerancePercent = 25;
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    auto frameRate = Fps::fromPeriodNsecs(1000);
+
+    const ftl::NonNull<DisplayModePtr> kMode = ftl::as_non_null(
+            createVrrDisplayMode(DisplayModeId(0), refreshRate,
+                                 hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
+                                                        frameRate.getPeriodNsecs())}));
+    std::shared_ptr<VSyncPredictor> vrrTracker =
+            std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
+                                             kOutlierTolerancePercent, mVsyncTrackerCallback);
+    std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
+            std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
+    TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
+                                vrrTracker,
+                                vrrSelectorPtr,
+                                sp<VsyncModulator>::make(VsyncConfigSet{}),
+                                mSchedulerCallback,
+                                mVsyncTrackerCallback};
+
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+    vrrTracker->addVsyncTimestamp(0);
+
+    // Next frame at refresh rate as no previous frame
+    EXPECT_EQ(refreshRate,
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(0)));
+
+    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(500)));
+    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(1500)));
+
+    // Change render rate
+    frameRate = Fps::fromPeriodNsecs(2000);
+    vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+
+    EXPECT_EQ(Fps::fromPeriodNsecs(2000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(2500)));
+    EXPECT_EQ(Fps::fromPeriodNsecs(2000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(4500)));
+}
+
+TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
+    // resyncAllToHardwareVsync will result in requesting hardware VSYNC on both displays, since
+    // they are both on.
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1);
+
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+    mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
+
+    static constexpr bool kDisallow = true;
+    mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+    mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+    static constexpr bool kAllowToEnable = true;
+    mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllDoNotAllow) FTL_FAKE_GUARD(kMainThreadContext) {
+    // Without setting allowToEnable to true, resyncAllToHardwareVsync does not
+    // result in requesting hardware VSYNC.
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, _)).Times(0);
+
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+    static constexpr bool kDisallow = true;
+    mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+
+    static constexpr bool kAllowToEnable = false;
+    mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllSkipsOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+
+    // resyncAllToHardwareVsync will result in requesting hardware VSYNC on display 1, which is on,
+    // but not on display 2, which is off.
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, _)).Times(0);
+
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
+    ASSERT_EQ(hal::PowerMode::OFF, mScheduler->getDisplayPowerMode(kDisplayId2));
+
+    static constexpr bool kDisallow = true;
+    mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+    mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+    static constexpr bool kAllowToEnable = true;
+    mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllLegacyAppliesToOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
+
+    // In the legacy code, prior to the flag, resync applied to OFF displays.
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+    EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1);
+
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
+    ASSERT_EQ(hal::PowerMode::OFF, mScheduler->getDisplayPowerMode(kDisplayId2));
+
+    static constexpr bool kDisallow = true;
+    mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+    mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+    static constexpr bool kAllowToEnable = true;
+    mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
 }
 
 class AttachedChoreographerTest : public SchedulerTest {
diff --git a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
index 05f9eed..8615035 100644
--- a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
@@ -44,8 +44,8 @@
     ASSERT_EQ(mMappings.getThresholdForAppId(kAppId2).value(), kThreshold2);
 }
 
-TEST_F(SmallAreaDetectionAllowMappingsTest, testSetThesholdForAppId) {
-    mMappings.setThesholdForAppId(kAppId1, kThreshold1);
+TEST_F(SmallAreaDetectionAllowMappingsTest, testSetThresholdForAppId) {
+    mMappings.setThresholdForAppId(kAppId1, kThreshold1);
     ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), kThreshold1);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index aeac80d..7ad97a2 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -46,7 +46,8 @@
 
         setupScheduler(selectorPtr);
 
-        mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                           DisplayHotplugEvent::CONNECTED);
         mFlinger.configureAndCommit();
 
         auto vsyncController = std::make_unique<mock::VsyncController>();
@@ -56,6 +57,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 +142,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 +152,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 +164,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()->mode.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 +178,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 +190,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 +205,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()->mode.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 +222,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 +232,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 +253,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()->mode.modePtr->getId(), kModeId120);
 
     EXPECT_CALL(*mComposer,
                 setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
@@ -255,20 +263,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()->mode.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 +284,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()->mode.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 +320,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()->mode.modePtr->getId() != modeId) {
+        *result_listener << "Unexpected desired mode " << modeId;
         return false;
     }
 
@@ -336,9 +344,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->mode.modePtr->getId();
         return false;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index 94d517a..b620830 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -110,7 +110,7 @@
     EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), display.isPrimary());
 
     std::optional<DisplayDeviceState::Physical> expectedPhysical;
-    if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+    if (Case::Display::CONNECTION_TYPE::value) {
         const auto displayId = PhysicalDisplayId::tryCast(Case::Display::DISPLAY_ID::get());
         ASSERT_TRUE(displayId);
         const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
index ed8d909..844b96c 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
@@ -188,5 +188,38 @@
     scheduler.onHardwareVsyncRequest(mOuterDisplay->getPhysicalId(), true);
 }
 
+TEST_F(FoldableTest, requestVsyncOnPowerOn) {
+    EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, true))
+            .Times(1);
+    EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kOuterDisplayId, true))
+            .Times(1);
+
+    mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+    mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+}
+
+TEST_F(FoldableTest, disableVsyncOnPowerOffPacesetter) {
+    // When the device boots, the inner display should be the pacesetter.
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+
+    testing::InSequence seq;
+    EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, true))
+            .Times(1);
+    EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kOuterDisplayId, true))
+            .Times(1);
+
+    // Turning off the pacesetter will result in disabling VSYNC.
+    EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, false))
+            .Times(1);
+
+    mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+    mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+    mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::OFF);
+
+    // Other display is now the pacesetter.
+    ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 1210d0b..a270dc9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -27,10 +27,10 @@
     EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(2);
 
     constexpr HWDisplayId hwcDisplayId1 = 456;
-    mFlinger.onComposerHalHotplug(hwcDisplayId1, Connection::CONNECTED);
+    mFlinger.onComposerHalHotplugEvent(hwcDisplayId1, DisplayHotplugEvent::CONNECTED);
 
     constexpr HWDisplayId hwcDisplayId2 = 654;
-    mFlinger.onComposerHalHotplug(hwcDisplayId2, Connection::DISCONNECTED);
+    mFlinger.onComposerHalHotplugEvent(hwcDisplayId2, DisplayHotplugEvent::DISCONNECTED);
 
     const auto& pendingEvents = mFlinger.mutablePendingHotplugEvents();
     ASSERT_EQ(2u, pendingEvents.size());
@@ -45,7 +45,7 @@
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     constexpr HWDisplayId displayId1 = 456;
-    mFlinger.onComposerHalHotplug(displayId1, Connection::DISCONNECTED);
+    mFlinger.onComposerHalHotplugEvent(displayId1, DisplayHotplugEvent::DISCONNECTED);
     mFlinger.configure();
 
     // The configure stage should consume the hotplug queue and produce a display transaction.
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index cf3fab3..15fe600 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 {
@@ -68,11 +73,13 @@
 
 struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant {
     static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
-        setupVsyncNoCallExpectations(test);
+        EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, true)).Times(1);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
     }
 
     static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
-        setupVsyncNoCallExpectations(test);
+        EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, false)).Times(1);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
     }
 };
 
@@ -94,7 +101,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);
@@ -292,6 +300,11 @@
 // A sample configuration for the external display.
 // In addition to not having event thread support, we emulate not having doze
 // support.
+// TODO (b/267483230): ExternalDisplay supports the features tracked in
+// DispSyncIsSupportedVariant, but is the follower, so the
+// expectations set by DispSyncIsSupportedVariant don't match (wrong schedule).
+// We need a way to retrieve the proper DisplayId from
+// setupResetModelCallExpectations (or pass it in).
 template <typename TransitionVariant>
 using ExternalDisplayPowerCase =
         DisplayPowerCase<ExternalDisplayVariant, DozeNotSupportedVariant<ExternalDisplayVariant>,
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 8b6f0f1..2a1b88e 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -78,10 +78,11 @@
 
     auto refreshRateSelector() { return pacesetterSelectorPtr(); }
 
-    void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+    void registerDisplay(
+            PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+            std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
         registerDisplay(displayId, std::move(selectorPtr),
-                        std::make_unique<mock::VsyncController>(),
-                        std::make_shared<mock::VSyncTracker>());
+                        std::make_unique<mock::VsyncController>(), vsyncTracker);
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
@@ -114,6 +115,15 @@
         Scheduler::setPacesetterDisplay(displayId);
     }
 
+    std::optional<hal::PowerMode> getDisplayPowerMode(PhysicalDisplayId id) {
+        ftl::FakeGuard guard1(kMainThreadContext);
+        ftl::FakeGuard guard2(mDisplayLock);
+        return mDisplays.get(id).transform(
+                [](const Display& display) { return display.powerMode; });
+    }
+
+    using Scheduler::resyncAllToHardwareVsync;
+
     auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
     auto& mutableLayerHistory() { return mLayerHistory; }
     auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bca14f5..8ba6bf8 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},
@@ -463,8 +466,8 @@
         mFlinger->commitTransactionsLocked(transactionFlags);
     }
 
-    void onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) {
-        mFlinger->onComposerHalHotplug(hwcDisplayId, connection);
+    void onComposerHalHotplugEvent(hal::HWDisplayId hwcDisplayId, DisplayHotplugEvent event) {
+        mFlinger->onComposerHalHotplugEvent(hwcDisplayId, event);
     }
 
     auto setDisplayStateLocked(const DisplayState& s) {
@@ -490,12 +493,13 @@
     auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
                           SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-                          bool forSystem, bool regionSampling) {
+                          bool regionSampling) {
         ScreenCaptureResults captureResults;
         return FTL_FAKE_GUARD(kMainThreadContext,
                               mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
-                                                         buffer, forSystem, regionSampling,
-                                                         false /* grayscale */, captureResults));
+                                                         buffer, regionSampling,
+                                                         false /* grayscale */,
+                                                         false /* isProtected */, captureResults));
     }
 
     auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
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/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 3b74f0a..d649679 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -86,9 +86,10 @@
     MOCK_METHOD3(getReleaseFences, Error(Display, std::vector<Layer>*, std::vector<int>*));
     MOCK_METHOD2(presentDisplay, Error(Display, int*));
     MOCK_METHOD2(setActiveConfig, Error(Display, Config));
-    MOCK_METHOD6(setClientTarget,
-                 Error(Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
-                       const std::vector<IComposerClient::Rect>&));
+    MOCK_METHOD(Error, setClientTarget,
+                (Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
+                 const std::vector<IComposerClient::Rect>&, float),
+                (override));
     MOCK_METHOD3(setColorMode, Error(Display, ColorMode, RenderIntent));
     MOCK_METHOD2(setColorTransform, Error(Display, const float*));
     MOCK_METHOD3(setOutputBuffer, Error(Display, const native_handle_t*, int));
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/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index a7ddb6d..7413235 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -66,8 +66,8 @@
                 ((std::unordered_map<Layer *, android::sp<android::Fence>> *)), (const, override));
     MOCK_METHOD(hal::Error, present, (android::sp<android::Fence> *), (override));
     MOCK_METHOD(hal::Error, setClientTarget,
-                (uint32_t, const android::sp<android::GraphicBuffer> &,
-                 const android::sp<android::Fence> &, hal::Dataspace),
+                (uint32_t, const android::sp<android::GraphicBuffer>&,
+                 const android::sp<android::Fence>&, hal::Dataspace, float),
                 (override));
     MOCK_METHOD(hal::Error, setColorMode, (hal::ColorMode, hal::RenderIntent), (override));
     MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index d635508..8e8eb1d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -36,11 +36,10 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
-    MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+    MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
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"],
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index 7e382a3..63a2bd0 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -6,10 +6,6 @@
         // TODO(b/293603710): Fix flakiness
         {
           "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
-        },
-        // TODO(b/293623689): Fix flakiness
-        {
-          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleMultipleCallbacksRunsInDelayOrder"
         }
       ]
     }