Merge "Expose InputConsumer::probablyHasInput" into main
diff --git a/Android.bp b/Android.bp
index 7f1ef67..8c4dfbb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,12 @@
     ],
 }
 
+aidl_library {
+    name: "PersistableBundle_aidl",
+    hdrs: ["aidl/binder/android/os/PersistableBundle.aidl"],
+    strip_import_prefix: "aidl/binder",
+}
+
 cc_library_headers {
     name: "libandroid_headers_private",
     export_include_dirs: ["include/private"],
diff --git a/aidl/binder/android/os/PersistableBundle.aidl b/aidl/binder/android/os/PersistableBundle.aidl
index 493ecb4..248e973 100644
--- a/aidl/binder/android/os/PersistableBundle.aidl
+++ b/aidl/binder/android/os/PersistableBundle.aidl
@@ -17,4 +17,4 @@
 
 package android.os;
 
-@JavaOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h" ndk_header "android/persistable_bundle_aidl.h";
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 860a2d8..23f185e 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -104,6 +104,8 @@
         "libvintf",
         "libbinderdebug",
         "packagemanager_aidl-cpp",
+        "server_configurable_flags",
+        "device_policy_aconfig_flags_c_lib",
     ],
     srcs: [
         "DumpstateService.cpp",
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index c787113..ba0a38a 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -37,6 +37,8 @@
     Dumpstate* ds = nullptr;
     int32_t calling_uid = -1;
     std::string calling_package;
+    int32_t user_id = -1;
+    bool keep_bugreport_on_retrieval = false;
 };
 
 static binder::Status exception(uint32_t code, const std::string& msg,
@@ -60,7 +62,7 @@
 
 [[noreturn]] static void* dumpstate_thread_retrieve(void* data) {
     std::unique_ptr<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
-    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package);
+    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, ds_info->keep_bugreport_on_retrieval);
     MYLOGD("Finished retrieving a bugreport. Exiting.\n");
     exit(0);
 }
@@ -201,9 +203,10 @@
 }
 
 binder::Status DumpstateService::retrieveBugreport(
-    int32_t calling_uid, const std::string& calling_package,
+    int32_t calling_uid, const std::string& calling_package, int32_t user_id,
     android::base::unique_fd bugreport_fd,
     const std::string& bugreport_file,
+    const bool keep_bugreport_on_retrieval,
     const sp<IDumpstateListener>& listener) {
 
     ds_ = &(Dumpstate::GetInstance());
@@ -211,6 +214,8 @@
     ds_info->ds = ds_;
     ds_info->calling_uid = calling_uid;
     ds_info->calling_package = calling_package;
+    ds_info->user_id = user_id;
+    ds_info->keep_bugreport_on_retrieval = keep_bugreport_on_retrieval;
     ds_->listener_ = listener;
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
     // Use a /dev/null FD when initializing options since none is provided.
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index dd73319..7b76c36 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -48,8 +48,10 @@
 
     binder::Status retrieveBugreport(int32_t calling_uid,
                                      const std::string& calling_package,
+                                     int32_t user_id,
                                      android::base::unique_fd bugreport_fd,
                                      const std::string& bugreport_file,
+                                     const bool keep_bugreport_on_retrieval,
                                      const sp<IDumpstateListener>& listener)
                                      override;
 
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index fa9bcf3..97c470e 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -58,6 +58,9 @@
     // Defer user consent.
     const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2;
 
+    // Keep bugreport stored after retrieval.
+    const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4;
+
     /**
      * Speculatively pre-dumps UI data for a bugreport request that might come later.
      *
@@ -116,12 +119,16 @@
      *
      * @param callingUid UID of the original application that requested the report.
      * @param callingPackage package of the original application that requested the report.
+     * @param userId user Id of the original package that requested the report.
      * @param bugreportFd the file to which the zipped bugreport should be written
      * @param bugreportFile the path of the bugreport file
+     * @param keepBugreportOnRetrieval boolean to indicate if the bugreport should be kept in the
+     * platform after it has been retrieved by the caller.
      * @param listener callback for updates; optional
      */
-    void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage,
+    void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage, int userId,
                            FileDescriptor bugreportFd,
                            @utf8InCpp String bugreportFile,
+                           boolean keepBugreportOnRetrieval,
                            IDumpstateListener listener);
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 33f6d38..0bbd4a8 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -59,6 +59,7 @@
 #include <vector>
 
 #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
+#include <android_app_admin_flags.h>
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
@@ -177,6 +178,7 @@
 #define PROFILE_DATA_DIR_CUR "/data/misc/profiles/cur"
 #define PROFILE_DATA_DIR_REF "/data/misc/profiles/ref"
 #define XFRM_STAT_PROC_FILE "/proc/net/xfrm_stat"
+#define KERNEL_CONFIG "/proc/config.gz"
 #define WLUTIL "/vendor/xbin/wlutil"
 #define WMTRACE_DATA_DIR "/data/misc/wmtrace"
 #define OTA_METADATA_DIR "/metadata/ota"
@@ -244,7 +246,7 @@
 static const std::string DUMP_HALS_TASK = "DUMP HALS";
 static const std::string DUMP_BOARD_TASK = "dumpstate_board()";
 static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS";
-static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES";
+static const std::string SERIALIZE_PERFETTO_TRACE_TASK = "SERIALIZE PERFETTO TRACE";
 
 namespace android {
 namespace os {
@@ -1034,8 +1036,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"});
@@ -1086,10 +1086,16 @@
 
 static void MaybeAddSystemTraceToZip() {
     // This function copies into the .zip the system trace that was snapshotted
-    // by the early call to MaybeSnapshotSystemTrace(), if any background
+    // by the early call to MaybeSnapshotSystemTraceAsync(), if any background
     // tracing was happening.
-    if (!ds.has_system_trace_) {
-        // No background trace was happening at the time dumpstate was invoked.
+    bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
+    if (!system_trace_exists) {
+        // No background trace was happening at the time MaybeSnapshotSystemTraceAsync() was invoked
+        if (!PropertiesHelper::IsUserBuild()) {
+            MYLOGI(
+                "No system traces found. Check for previously uploaded traces by looking for "
+                "go/trace-uuid in logcat")
+        }
         return;
     }
     ds.AddZipEntry(
@@ -1236,7 +1242,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"});
@@ -1519,7 +1525,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.
@@ -1635,7 +1641,7 @@
 
     // Enqueue slow functions into the thread pool, if the parallel run is enabled.
     std::future<std::string> dump_hals, dump_incident_report, dump_board, dump_checkins,
-            dump_netstats_report, post_process_ui_traces;
+        dump_netstats_report;
     if (ds.dump_pool_) {
         // Pool was shutdown in DumpstateDefaultAfterCritical method in order to
         // drop root user. Restarts it.
@@ -1649,8 +1655,6 @@
         dump_board = ds.dump_pool_->enqueueTaskWithFd(
             DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
         dump_checkins = ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1);
-        post_process_ui_traces = ds.dump_pool_->enqueueTask(
-            POST_PROCESS_UI_TRACES_TASK, &Dumpstate::MaybePostProcessUiTraces, &ds);
     }
 
     // Dump various things. Note that anything that takes "long" (i.e. several seconds) should
@@ -1860,12 +1864,6 @@
                 DumpIncidentReport);
     }
 
-    if (ds.dump_pool_) {
-        WaitForTask(std::move(post_process_ui_traces));
-    } else {
-        RUN_SLOW_FUNCTION_AND_LOG(POST_PROCESS_UI_TRACES_TASK, MaybePostProcessUiTraces);
-    }
-
     MaybeAddUiTracesToZip();
 
     return Dumpstate::RunStatus::OK;
@@ -1954,6 +1952,8 @@
     DumpFile("PSI memory", "/proc/pressure/memory");
     DumpFile("PSI io", "/proc/pressure/io");
 
+    ds.AddZipEntry(ZIP_ROOT_DIR + KERNEL_CONFIG, KERNEL_CONFIG);
+
     RunCommand("SDK EXTENSIONS", {SDK_EXT_INFO, "--dump"},
                CommandOptions::WithTimeout(10).Always().DropRoot().Build());
 
@@ -2977,14 +2977,17 @@
     return status;
 }
 
-Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package) {
-    Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package);
+Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package,
+                                         const bool keep_bugreport_on_retrieval) {
+    Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package,
+                                                    keep_bugreport_on_retrieval);
     HandleRunStatus(status);
     return status;
 }
 
 Dumpstate::RunStatus  Dumpstate::RetrieveInternal(int32_t calling_uid,
-                                                  const std::string& calling_package) {
+                                                  const std::string& calling_package,
+                                                  const bool keep_bugreport_on_retrieval) {
   consent_callback_ = new ConsentCallback();
   const String16 incidentcompanion("incidentcompanion");
   sp<android::IBinder> ics(
@@ -3019,9 +3022,12 @@
 
   bool copy_succeeded =
       android::os::CopyFileToFd(path_, options_->bugreport_fd.get());
-  if (copy_succeeded) {
-    android::os::UnlinkAndLogOnError(path_);
+
+  if (copy_succeeded && (!android::app::admin::flags::onboarding_bugreport_v2_enabled()
+                         || !keep_bugreport_on_retrieval)) {
+        android::os::UnlinkAndLogOnError(path_);
   }
+
   return copy_succeeded ? Dumpstate::RunStatus::OK
                         : Dumpstate::RunStatus::ERROR;
 }
@@ -3071,7 +3077,9 @@
 }
 
 void Dumpstate::PreDumpUiData() {
+    auto snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
     MaybeSnapshotUiTraces();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
 }
 
 /*
@@ -3257,25 +3265,26 @@
     // duration is logged into MYLOG instead.
     PrintHeader();
 
-    bool is_dumpstate_restricted = options_->telephony_only
-                                   || options_->wifi_only
-                                   || options_->limited_only;
-    if (!is_dumpstate_restricted) {
-        // Invoke critical dumpsys first to preserve system state, before doing anything else.
-        RunDumpsysCritical();
-    }
-    MaybeTakeEarlyScreenshot();
+    std::future<std::string> snapshot_system_trace;
 
+    bool is_dumpstate_restricted =
+        options_->telephony_only || options_->wifi_only || options_->limited_only;
     if (!is_dumpstate_restricted) {
         // Snapshot the system trace now (if running) to avoid that dumpstate's
         // own activity pushes out interesting data from the trace ring buffer.
         // The trace file is added to the zip by MaybeAddSystemTraceToZip().
-        MaybeSnapshotSystemTrace();
+        snapshot_system_trace = MaybeSnapshotSystemTraceAsync();
+
+        // Invoke critical dumpsys to preserve system state, before doing anything else.
+        RunDumpsysCritical();
 
         // Snapshot the UI traces now (if running).
         // The trace files will be added to bugreport later.
         MaybeSnapshotUiTraces();
     }
+
+    MaybeTakeEarlyScreenshot();
+    MaybeWaitForSnapshotSystemTrace(std::move(snapshot_system_trace));
     onUiIntensiveBugreportDumpsFinished(calling_uid);
     MaybeCheckUserConsent(calling_uid, calling_package);
     if (options_->telephony_only) {
@@ -3371,24 +3380,59 @@
     TakeScreenshot();
 }
 
-void Dumpstate::MaybeSnapshotSystemTrace() {
-    // If a background system trace is happening and is marked as "suitable for
-    // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
-    // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
-    // case that no trace is ongoing, this command is a no-op.
-    // Note: this should not be enqueued as we need to freeze the trace before
-    // dumpstate starts. Otherwise the trace ring buffers will contain mostly
-    // the dumpstate's own activity which is irrelevant.
-    int res = RunCommand(
-        "SERIALIZE PERFETTO TRACE",
-        {"perfetto", "--save-for-bugreport"},
-        CommandOptions::WithTimeout(10)
-            .DropRoot()
-            .CloseAllFileDescriptorsOnExec()
-            .Build());
-    has_system_trace_ = res == 0;
-    // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
-    // file in the later stages.
+std::future<std::string> Dumpstate::MaybeSnapshotSystemTraceAsync() {
+    // When capturing traces via bugreport handler (BH), this function will be invoked twice:
+    // 1) When BH invokes IDumpstate::PreDumpUiData()
+    // 2) When BH invokes IDumpstate::startBugreport(flags = BUGREPORT_USE_PREDUMPED_UI_DATA)
+    // In this case we don't want to re-invoke perfetto in step 2.
+    // In all other standard invocation states, this function is invoked once
+    // without the flag BUGREPORT_USE_PREDUMPED_UI_DATA.
+    // This function must run asynchronously to avoid delaying MaybeTakeEarlyScreenshot() in the
+    // standard invocation states (b/316110955).
+    if (options_->use_predumped_ui_data) {
+        return {};
+    }
+
+    // Create temporary file for the command's output
+    std::string outPath = ds.bugreport_internal_dir_ + "/tmp_serialize_perfetto_trace";
+    auto outFd = android::base::unique_fd(TEMP_FAILURE_RETRY(
+        open(outPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+    if (outFd < 0) {
+        MYLOGE("Could not open %s to serialize perfetto trace.\n", outPath.c_str());
+        return {};
+    }
+
+    // If a stale file exists already, remove it.
+    unlink(SYSTEM_TRACE_SNAPSHOT);
+
+    MYLOGI("Launching async '%s'", SERIALIZE_PERFETTO_TRACE_TASK.c_str())
+    return std::async(
+        std::launch::async, [this, outPath = std::move(outPath), outFd = std::move(outFd)] {
+            // If a background system trace is happening and is marked as "suitable for
+            // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
+            // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
+            // case that no trace is ongoing, this command is a no-op.
+            // Note: this should not be enqueued as we need to freeze the trace before
+            // dumpstate starts. Otherwise the trace ring buffers will contain mostly
+            // the dumpstate's own activity which is irrelevant.
+            RunCommand(
+                SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"},
+                CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
+                false, outFd);
+            // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
+            // file in the later stages.
+
+            return outPath;
+        });
+}
+
+void Dumpstate::MaybeWaitForSnapshotSystemTrace(std::future<std::string> task) {
+    if (!task.valid()) {
+        return;
+    }
+
+    WaitForTask(std::move(task), SERIALIZE_PERFETTO_TRACE_TASK, STDOUT_FILENO);
 }
 
 void Dumpstate::MaybeSnapshotUiTraces() {
@@ -3413,33 +3457,6 @@
             "", command,
             CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
     }
-
-    // This command needs to be run as root
-    static const auto SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES = std::vector<std::string> {
-        "service", "call", "SurfaceFlinger", "1042"
-    };
-    // Empty name because it's not intended to be classified as a bugreport section.
-    // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport.
-    RunCommand(
-        "", SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES,
-        CommandOptions::WithTimeout(10).Always().AsRoot().RedirectStderr().Build());
-}
-
-void Dumpstate::MaybePostProcessUiTraces() {
-    if (PropertiesHelper::IsUserBuild()) {
-        return;
-    }
-
-    RunCommand(
-        // Empty name because it's not intended to be classified as a bugreport section.
-        // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport.
-        "", {
-            "/system/xbin/su", "system",
-            "/system/bin/layertracegenerator",
-            "/data/misc/wmtrace/transactions_trace.winscope",
-            "/data/misc/wmtrace/layers_trace_from_transactions.winscope"
-        },
-        CommandOptions::WithTimeout(120).Always().RedirectStderr().Build());
 }
 
 void Dumpstate::MaybeAddUiTracesToZip() {
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 0a032ec..20b2865 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -210,7 +210,9 @@
         BUGREPORT_USE_PREDUMPED_UI_DATA =
           android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
         BUGREPORT_FLAG_DEFER_CONSENT =
-          android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT
+          android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT,
+          BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
+                    android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
     };
 
     static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
@@ -361,7 +363,8 @@
      *
      * Initialize() dumpstate before calling this method.
      */
-    RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package);
+    RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package,
+                        const bool keep_bugreport_on_retrieval);
 
 
 
@@ -473,11 +476,6 @@
     // Whether it should take an screenshot earlier in the process.
     bool do_early_screenshot_ = false;
 
-    // This is set to true when the trace snapshot request in the early call to
-    // MaybeSnapshotSystemTrace(). When this is true, the later stages of
-    // dumpstate will append the trace to the zip archive.
-    bool has_system_trace_ = false;
-
     std::unique_ptr<Progress> progress_;
 
     // When set, defines a socket file-descriptor use to report progress to bugreportz
@@ -562,15 +560,16 @@
 
   private:
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
-    RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package);
+    RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package,
+                                const bool keep_bugreport_on_retrieval);
 
     RunStatus DumpstateDefaultAfterCritical();
     RunStatus dumpstate();
 
     void MaybeTakeEarlyScreenshot();
-    void MaybeSnapshotSystemTrace();
+    std::future<std::string> MaybeSnapshotSystemTraceAsync();
+    void MaybeWaitForSnapshotSystemTrace(std::future<std::string> task);
     void MaybeSnapshotUiTraces();
-    void MaybePostProcessUiTraces();
     void MaybeAddUiTracesToZip();
 
     void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid);
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index a417837..fc82886 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -1000,7 +1000,6 @@
 TEST_F(DumpstateTest, PreDumpUiData) {
     // These traces are always enabled, i.e. they are always pre-dumped
     const std::vector<std::filesystem::path> uiTraces = {
-        std::filesystem::path{"/data/misc/wmtrace/transactions_trace.winscope"},
         std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"},
         std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"},
     };
diff --git a/cmds/evemu-record/Android.bp b/cmds/evemu-record/Android.bp
new file mode 100644
index 0000000..1edacec
--- /dev/null
+++ b/cmds/evemu-record/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_binary {
+    name: "evemu-record",
+    srcs: ["main.rs"],
+    rustlibs: [
+        "libclap",
+        "liblibc",
+        "libnix",
+    ],
+}
diff --git a/cmds/evemu-record/OWNERS b/cmds/evemu-record/OWNERS
new file mode 100644
index 0000000..c88bfe9
--- /dev/null
+++ b/cmds/evemu-record/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/cmds/evemu-record/evdev.rs b/cmds/evemu-record/evdev.rs
new file mode 100644
index 0000000..35feea1
--- /dev/null
+++ b/cmds/evemu-record/evdev.rs
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Wrappers for the Linux evdev APIs.
+
+use std::fs::File;
+use std::io;
+use std::mem;
+use std::os::fd::{AsRawFd, OwnedFd};
+use std::path::Path;
+
+use libc::c_int;
+use nix::sys::time::TimeVal;
+
+pub const SYN_CNT: usize = 0x10;
+pub const KEY_CNT: usize = 0x300;
+pub const REL_CNT: usize = 0x10;
+pub const ABS_CNT: usize = 0x40;
+pub const MSC_CNT: usize = 0x08;
+pub const SW_CNT: usize = 0x11;
+pub const LED_CNT: usize = 0x10;
+pub const SND_CNT: usize = 0x08;
+pub const REP_CNT: usize = 0x02;
+
+// Disable naming warnings, as these are supposed to match the EV_ constants in input-event-codes.h.
+#[allow(non_camel_case_types)]
+// Some of these types aren't referenced for evemu purposes, but are included for completeness.
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(u16)]
+pub enum EventType {
+    SYN = 0x00,
+    KEY = 0x01,
+    REL = 0x02,
+    ABS = 0x03,
+    MSC = 0x04,
+    SW = 0x05,
+    LED = 0x11,
+    SND = 0x12,
+    REP = 0x14,
+    FF = 0x15,
+    PWR = 0x16,
+    FF_STATUS = 0x17,
+}
+
+impl EventType {
+    fn code_count(&self) -> usize {
+        match self {
+            EventType::SYN => SYN_CNT,
+            EventType::KEY => KEY_CNT,
+            EventType::REL => REL_CNT,
+            EventType::ABS => ABS_CNT,
+            EventType::MSC => MSC_CNT,
+            EventType::SW => SW_CNT,
+            EventType::LED => LED_CNT,
+            EventType::SND => SND_CNT,
+            EventType::REP => REP_CNT,
+            _ => {
+                panic!("Event type {self:?} does not have a defined code count.");
+            }
+        }
+    }
+}
+
+pub const EVENT_TYPES_WITH_BITMAPS: [EventType; 7] = [
+    EventType::KEY,
+    EventType::REL,
+    EventType::ABS,
+    EventType::MSC,
+    EventType::SW,
+    EventType::LED,
+    EventType::SND,
+];
+
+const INPUT_PROP_CNT: usize = 32;
+
+/// The `ioctl_*!` macros create public functions by default, so this module makes them private.
+mod ioctl {
+    use nix::{ioctl_read, ioctl_read_buf};
+
+    ioctl_read!(eviocgid, b'E', 0x02, super::DeviceId);
+    ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
+    ioctl_read_buf!(eviocgprop, b'E', 0x09, u8);
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct DeviceId {
+    pub bus_type: u16,
+    pub vendor: u16,
+    pub product: u16,
+    pub version: u16,
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct AbsoluteAxisInfo {
+    pub value: i32,
+    pub minimum: i32,
+    pub maximum: i32,
+    pub fuzz: i32,
+    pub flat: i32,
+    pub resolution: i32,
+}
+
+#[repr(C)]
+pub struct InputEvent {
+    pub time: TimeVal,
+    pub type_: u16,
+    pub code: u16,
+    pub value: i32,
+}
+
+impl InputEvent {
+    pub fn offset_time_by(&self, offset: TimeVal) -> InputEvent {
+        InputEvent { time: self.time - offset, ..*self }
+    }
+}
+
+impl Default for InputEvent {
+    fn default() -> Self {
+        InputEvent { time: TimeVal::new(0, 0), type_: 0, code: 0, value: 0 }
+    }
+}
+
+/// An object representing an input device using Linux's evdev protocol.
+pub struct Device {
+    fd: OwnedFd,
+}
+
+/// # Safety
+///
+/// `ioctl` must be safe to call with the given file descriptor and a pointer to a buffer of
+/// `initial_buf_size` `u8`s.
+unsafe fn buf_from_ioctl(
+    ioctl: unsafe fn(c_int, &mut [u8]) -> nix::Result<c_int>,
+    fd: &OwnedFd,
+    initial_buf_size: usize,
+) -> Result<Vec<u8>, nix::errno::Errno> {
+    let mut buf = vec![0; initial_buf_size];
+    // SAFETY:
+    // Here we're relying on the safety guarantees for `ioctl` made by the caller.
+    match unsafe { ioctl(fd.as_raw_fd(), buf.as_mut_slice()) } {
+        Ok(len) if len < 0 => {
+            panic!("ioctl returned invalid length {len}");
+        }
+        Ok(len) => {
+            buf.truncate(len as usize);
+            Ok(buf)
+        }
+        Err(err) => Err(err),
+    }
+}
+
+impl Device {
+    /// Opens a device from the evdev node at the given path.
+    pub fn open(path: &Path) -> io::Result<Device> {
+        Ok(Device { fd: OwnedFd::from(File::open(path)?) })
+    }
+
+    /// Returns the name of the device, as set by the relevant kernel driver.
+    pub fn name(&self) -> Result<String, nix::errno::Errno> {
+        // There's no official maximum length for evdev device names. The Linux HID driver
+        // currently supports names of at most 151 bytes (128 from the device plus a suffix of up
+        // to 23 bytes). 256 seems to be the buffer size most commonly used in evdev bindings, so
+        // we use it here.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+        // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+        // str_to_user function will truncate the string to that length.
+        let mut buf = unsafe { buf_from_ioctl(ioctl::eviocgname, &self.fd, 256)? };
+        assert!(!buf.is_empty(), "buf is too short for an empty null-terminated string");
+        assert_eq!(buf.pop().unwrap(), 0, "buf is not a null-terminated string");
+        Ok(String::from_utf8_lossy(buf.as_slice()).into_owned())
+    }
+
+    pub fn ids(&self) -> Result<DeviceId, nix::errno::Errno> {
+        let mut ids = DeviceId::default();
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to ids is valid because we just allocated it.
+        unsafe { ioctl::eviocgid(self.fd.as_raw_fd(), &mut ids) }.map(|_| ids)
+    }
+
+    pub fn properties_bitmap(&self) -> Result<Vec<u8>, nix::errno::Errno> {
+        let buf_size = (INPUT_PROP_CNT + 7) / 8;
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+        // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+        // str_to_user function will truncate the string to that length.
+        unsafe { buf_from_ioctl(ioctl::eviocgprop, &self.fd, buf_size) }
+    }
+
+    pub fn bitmap_for_event_type(&self, event_type: EventType) -> nix::Result<Vec<u8>> {
+        let buf_size = (event_type.code_count() + 7) / 8;
+        let mut buf = vec![0; buf_size];
+
+        // The EVIOCGBIT ioctl can't be bound using ioctl_read_buf! like the others, since it uses
+        // part of its ioctl code as an additional parameter, for the event type. Hence this unsafe
+        // block is a manual expansion of ioctl_read_buf!.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We prevent the retrieved data from overflowing buf by passing in the size of buf to the
+        // ioctl, meaning that the kernel's str_to_user function will truncate the string to that
+        // length. We also panic if the ioctl returns a length longer than buf, hopefully before the
+        // overflow can do any damage.
+        match nix::errno::Errno::result(unsafe {
+            libc::ioctl(
+                self.fd.as_raw_fd(),
+                nix::request_code_read!(b'E', 0x20 + event_type as u16, buf.len())
+                    as nix::sys::ioctl::ioctl_num_type,
+                buf.as_mut_ptr(),
+            )
+        }) {
+            Ok(len) if len < 0 => {
+                panic!("EVIOCGBIT returned invalid length {len} for event type {event_type:?}");
+            }
+            Ok(len) => {
+                buf.truncate(len as usize);
+                Ok(buf)
+            }
+            Err(err) => Err(err),
+        }
+    }
+
+    pub fn supported_axes_of_type(&self, event_type: EventType) -> nix::Result<Vec<u16>> {
+        let mut axes = Vec::new();
+        for (i, byte_ref) in self.bitmap_for_event_type(event_type)?.iter().enumerate() {
+            let mut byte = *byte_ref;
+            for j in 0..8 {
+                if byte & 1 == 1 {
+                    axes.push((i * 8 + j) as u16);
+                }
+                byte >>= 1;
+            }
+        }
+        Ok(axes)
+    }
+
+    pub fn absolute_axis_info(&self, axis: u16) -> nix::Result<AbsoluteAxisInfo> {
+        let mut info = AbsoluteAxisInfo::default();
+        // The EVIOCGABS ioctl can't be bound using ioctl_read! since it uses part of its ioctl code
+        // as an additional parameter, for the axis code. Hence this unsafe block is a manual
+        // expansion of ioctl_read!.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to info is valid because we just allocated it.
+        nix::errno::Errno::result(unsafe {
+            nix::libc::ioctl(
+                self.fd.as_raw_fd(),
+                nix::request_code_read!(b'E', 0x40 + axis, mem::size_of::<AbsoluteAxisInfo>()),
+                &mut info,
+            )
+        })
+        .map(|_| info)
+    }
+
+    pub fn read_event(&self) -> nix::Result<InputEvent> {
+        let mut event = InputEvent::default();
+
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to event is valid because we just allocated it, and that the
+        // data structures match up because InputEvent is repr(C) and all its members are repr(C)
+        // or primitives that support all representations without niches.
+        nix::errno::Errno::result(unsafe {
+            libc::read(
+                self.fd.as_raw_fd(),
+                &mut event as *mut _ as *mut std::ffi::c_void,
+                mem::size_of::<InputEvent>(),
+            )
+        })
+        .map(|_| event)
+    }
+}
diff --git a/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
new file mode 100644
index 0000000..c30c00f
--- /dev/null
+++ b/cmds/evemu-record/main.rs
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A Rust implementation of the evemu-record command from the [FreeDesktop evemu suite][evemu] of
+//! tools.
+//!
+//! [evemu]: https://gitlab.freedesktop.org/libevdev/evemu
+
+use std::cmp;
+use std::error::Error;
+use std::fs;
+use std::io;
+use std::io::{BufRead, Write};
+use std::path::PathBuf;
+
+use clap::{Parser, ValueEnum};
+use nix::sys::time::TimeVal;
+
+mod evdev;
+
+/// Records evdev events from an input device in a format compatible with the FreeDesktop evemu
+/// library.
+#[derive(Parser, Debug)]
+struct Args {
+    /// The path to the input device to record. If omitted, offers a list of devices to choose from.
+    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 {
+    fn read_u32() -> Result<u32, std::num::ParseIntError> {
+        io::stdin().lock().lines().next().unwrap().unwrap().parse::<u32>()
+    }
+    let mut choice = read_u32();
+    while choice.is_err() || choice.clone().unwrap() > max {
+        eprint!("Enter a number between 0 and {max} inclusive: ");
+        choice = read_u32();
+    }
+    choice.unwrap()
+}
+
+fn pick_input_device() -> Result<PathBuf, io::Error> {
+    eprintln!("Available devices:");
+    let mut entries =
+        fs::read_dir("/dev/input")?.filter_map(|entry| entry.ok()).collect::<Vec<_>>();
+    entries.sort_by_key(|entry| entry.path());
+    let mut highest_number = 0;
+    for entry in entries {
+        let path = entry.path();
+        let file_name = path.file_name().unwrap().to_str().unwrap();
+        if path.is_dir() || !file_name.starts_with("event") {
+            continue;
+        }
+        let number = file_name.strip_prefix("event").unwrap().parse::<u32>();
+        if number.is_err() {
+            continue;
+        }
+        let number = number.unwrap();
+        match evdev::Device::open(path.as_path()) {
+            Ok(dev) => {
+                highest_number = cmp::max(highest_number, number);
+                eprintln!(
+                    "{}:\t{}",
+                    path.display(),
+                    dev.name().unwrap_or("[could not read name]".to_string()),
+                );
+            }
+            Err(_) => {
+                eprintln!("Couldn't open {}", path.display());
+            }
+        }
+    }
+    eprint!("Select the device event number [0-{highest_number}]: ");
+    let choice = get_choice(highest_number);
+    Ok(PathBuf::from(format!("/dev/input/event{choice}")))
+}
+
+fn print_device_description(
+    device: &evdev::Device,
+    output: &mut impl Write,
+) -> Result<(), Box<dyn Error>> {
+    // TODO(b/302297266): report LED and SW states, then bump the version to EVEMU 1.3.
+    writeln!(output, "# EVEMU 1.2")?;
+    writeln!(output, "N: {}", device.name()?)?;
+
+    let ids = device.ids()?;
+    writeln!(
+        output,
+        "I: {:04x} {:04x} {:04x} {:04x}",
+        ids.bus_type, ids.vendor, ids.product, ids.version,
+    )?;
+
+    fn print_in_8_byte_chunks(
+        output: &mut impl Write,
+        prefix: &str,
+        data: &Vec<u8>,
+    ) -> Result<(), io::Error> {
+        for (i, byte) in data.iter().enumerate() {
+            if i % 8 == 0 {
+                write!(output, "{prefix}")?;
+            }
+            write!(output, " {:02x}", byte)?;
+            if (i + 1) % 8 == 0 {
+                writeln!(output)?;
+            }
+        }
+        if data.len() % 8 != 0 {
+            for _ in (data.len() % 8)..8 {
+                write!(output, " 00")?;
+            }
+            writeln!(output)?;
+        }
+        Ok(())
+    }
+
+    let props = device.properties_bitmap()?;
+    print_in_8_byte_chunks(output, "P:", &props)?;
+
+    // The SYN event type can't be queried through the EVIOCGBIT ioctl, so just hard-code it to
+    // SYN_REPORT, SYN_CONFIG, and SYN_DROPPED.
+    writeln!(output, "B: 00 0b 00 00 00 00 00 00 00")?;
+    for event_type in evdev::EVENT_TYPES_WITH_BITMAPS {
+        let bits = device.bitmap_for_event_type(event_type)?;
+        print_in_8_byte_chunks(output, format!("B: {:02x}", event_type as u16).as_str(), &bits)?;
+    }
+
+    for axis in device.supported_axes_of_type(evdev::EventType::ABS)? {
+        let info = device.absolute_axis_info(axis)?;
+        writeln!(
+            output,
+            "A: {axis:02x} {} {} {} {} {}",
+            info.minimum, info.maximum, info.fuzz, info.flat, info.resolution
+        )?;
+    }
+    Ok(())
+}
+
+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!(
+            output,
+            "E: {}.{:06} {:04x} {:04x} {:04}",
+            event.time.tv_sec(),
+            event.time.tv_usec(),
+            event.type_,
+            event.code,
+            event.value,
+        )?;
+        Ok(())
+    }
+    let event = device.read_event()?;
+    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()?;
+        print_event(output, &event.offset_time_by(start_time))?;
+    }
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+    let args = Args::parse();
+
+    let device_path = args.device.unwrap_or_else(|| pick_input_device().unwrap());
+
+    let device = evdev::Device::open(device_path.as_path())?;
+    let mut output = match args.output_file {
+        Some(path) => Box::new(fs::File::create(path)?) as Box<dyn Write>,
+        None => Box::new(io::stdout().lock()),
+    };
+    print_device_description(&device, &mut output)?;
+    print_events(&device, &mut output, args.timestamp_base)?;
+    Ok(())
+}
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index ac101ec..334bae4 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -34,7 +34,6 @@
         "unique_file.cpp",
         "utils.cpp",
         "utils_default.cpp",
-        "view_compiler.cpp",
         ":installd_aidl",
     ],
     shared_libs: [
@@ -254,7 +253,6 @@
         "unique_file.cpp",
         "utils.cpp",
         "utils_default.cpp",
-        "view_compiler.cpp",
     ],
 
     static_libs: [
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 073d0c4..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>
@@ -69,7 +70,6 @@
 #include "installd_deps.h"
 #include "otapreopt_utils.h"
 #include "utils.h"
-#include "view_compiler.h"
 
 #include "CacheTracker.h"
 #include "CrateManager.h"
@@ -472,6 +472,49 @@
     return NO_ERROR;
 }
 
+constexpr const char kXattrRestoreconInProgress[] = "user.restorecon_in_progress";
+
+static std::string lgetfilecon(const std::string& path) {
+    char* context;
+    if (::lgetfilecon(path.c_str(), &context) < 0) {
+        PLOG(ERROR) << "Failed to lgetfilecon for " << path;
+        return {};
+    }
+    std::string result{context};
+    free(context);
+    return result;
+}
+
+static bool getRestoreconInProgress(const std::string& path) {
+    bool inProgress = false;
+    if (getxattr(path.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress)) !=
+        sizeof(inProgress)) {
+        if (errno != ENODATA) {
+            PLOG(ERROR) << "Failed to check in-progress restorecon for " << path;
+        }
+        return false;
+    }
+    return inProgress;
+}
+
+struct RestoreconInProgress {
+    explicit RestoreconInProgress(const std::string& path) : mPath(path) {
+        bool inProgress = true;
+        if (setxattr(mPath.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress),
+                     0) != 0) {
+            PLOG(ERROR) << "Failed to set in-progress restorecon for " << path;
+        }
+    }
+    ~RestoreconInProgress() {
+        if (removexattr(mPath.c_str(), kXattrRestoreconInProgress) < 0) {
+            PLOG(ERROR) << "Failed to clear in-progress restorecon for " << mPath;
+        }
+    }
+
+private:
+    const std::string& mPath;
+};
+
 /**
  * Perform restorecon of the given path, but only perform recursive restorecon
  * if the label of that top-level file actually changed.  This can save us
@@ -480,56 +523,70 @@
 static int restorecon_app_data_lazy(const std::string& path, const std::string& seInfo, uid_t uid,
         bool existing) {
     ScopedTrace tracer("restorecon-lazy");
-    int res = 0;
-    char* before = nullptr;
-    char* after = nullptr;
     if (!existing) {
         ScopedTrace tracer("new-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;
-            goto fail;
+            return -1;
         }
-        return res;
+        return 0;
     }
 
-    // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by
-    // libselinux. Not needed here.
-    if (lgetfilecon(path.c_str(), &before) < 0) {
-        PLOG(ERROR) << "Failed before getfilecon for " << path;
-        goto fail;
-    }
-    if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
-        PLOG(ERROR) << "Failed top-level restorecon for " << path;
-        goto fail;
-    }
-    if (lgetfilecon(path.c_str(), &after) < 0) {
-        PLOG(ERROR) << "Failed after getfilecon for " << path;
-        goto fail;
+    // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by libselinux. Not needed here.
+
+    // Check to see if there was an interrupted operation.
+    bool inProgress = getRestoreconInProgress(path);
+    std::string before, after;
+    if (!inProgress) {
+        if (before = lgetfilecon(path); before.empty()) {
+            PLOG(ERROR) << "Failed before getfilecon for " << path;
+            return -1;
+        }
+        if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
+            PLOG(ERROR) << "Failed top-level restorecon for " << path;
+            return -1;
+        }
+        if (after = lgetfilecon(path); after.empty()) {
+            PLOG(ERROR) << "Failed after getfilecon for " << path;
+            return -1;
+        }
     }
 
     // If the initial top-level restorecon above changed the label, then go
     // back and restorecon everything recursively
-    if (strcmp(before, after)) {
-        ScopedTrace tracer("label-change");
+    if (inProgress || before != after) {
         if (existing) {
             LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
-                    << path << "; running recursive restorecon";
+                       << path << "; running recursive restorecon";
         }
-        if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
-                SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
-            PLOG(ERROR) << "Failed recursive restorecon for " << path;
-            goto fail;
+
+        auto restorecon = [path, seInfo, uid]() {
+            ScopedTrace tracer("label-change");
+
+            // 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;
+            }
         }
     }
 
-    goto done;
-fail:
-    res = -1;
-done:
-    free(before);
-    free(after);
-    return res;
+    return 0;
 }
 static bool internal_storage_has_project_id() {
     // The following path is populated in setFirstBoot, so if this file is present
@@ -3258,17 +3315,6 @@
     return ok();
 }
 
-binder::Status InstalldNativeService::compileLayouts(const std::string& apkPath,
-                                                     const std::string& packageName,
-                                                     const std ::string& outDexFile, int uid,
-                                                     bool* _aidl_return) {
-    const char* apk_path = apkPath.c_str();
-    const char* package_name = packageName.c_str();
-    const char* out_dex_file = outDexFile.c_str();
-    *_aidl_return = android::installd::view_compiler(apk_path, package_name, out_dex_file, uid);
-    return *_aidl_return ? ok() : error("viewcompiler failed");
-}
-
 binder::Status InstalldNativeService::linkNativeLibraryDirectory(
         const std::optional<std::string>& uuid, const std::string& packageName,
         const std::string& nativeLibPath32, int32_t userId) {
@@ -3295,7 +3341,7 @@
     }
 
     char *con = nullptr;
-    if (lgetfilecon(pkgdir, &con) < 0) {
+    if (::lgetfilecon(pkgdir, &con) < 0) {
         return error("Failed to lgetfilecon " + _pkgdir);
     }
 
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 1ec092d..1b56144 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -146,9 +146,6 @@
 
     binder::Status controlDexOptBlocking(bool block);
 
-    binder::Status compileLayouts(const std::string& apkPath, const std::string& packageName,
-                                  const std::string& outDexFile, int uid, bool* _aidl_return);
-
     binder::Status rmdex(const std::string& codePath, const std::string& instructionSet);
 
     binder::Status mergeProfiles(int32_t uid, const std::string& packageName,
diff --git a/cmds/installd/OWNERS b/cmds/installd/OWNERS
index 643b2c2..e9fb85b 100644
--- a/cmds/installd/OWNERS
+++ b/cmds/installd/OWNERS
@@ -1,11 +1,10 @@
 set noparent
 
-calin@google.com
 jsharkey@android.com
 maco@google.com
 mast@google.com
+jiakaiz@google.com
 narayan@google.com
 ngeoffray@google.com
 rpl@google.com
-toddke@google.com
 patb@google.com
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 8893e38..f5a7709 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -70,8 +70,6 @@
     // Blocks (when block is true) or unblock (when block is false) dexopt.
     // Blocking also invloves cancelling the currently running dexopt.
     void controlDexOptBlocking(boolean block);
-    boolean compileLayouts(@utf8InCpp String apkPath, @utf8InCpp String packageName,
-            @utf8InCpp String outDexFile, int uid);
 
     void rmdex(@utf8InCpp String codePath, @utf8InCpp String instructionSet);
 
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 822ab7f..8eb7458 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -514,6 +514,8 @@
         // Make sure dex2oat is run with background priority.
         dexopt_flags |= DEXOPT_BOOTCOMPLETE | DEXOPT_IDLE_BACKGROUND_JOB;
 
+        parameters_.compilation_reason = "ab-ota";
+
         int res = dexopt(parameters_.apk_path,
                          parameters_.uid,
                          parameters_.pkgName,
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index c40caf5..c86adef 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -353,7 +353,7 @@
     // Now go on and read dexopt lines from stdin and pass them on to otapreopt.
 
     int count = 1;
-    for (std::array<char, 1000> linebuf;
+    for (std::array<char, 10000> linebuf;
          std::cin.clear(), std::cin.getline(&linebuf[0], linebuf.size()); ++count) {
         // Subtract one from gcount() since getline() counts the newline.
         std::string line(&linebuf[0], std::cin.gcount() - 1);
diff --git a/cmds/installd/view_compiler.cpp b/cmds/installd/view_compiler.cpp
deleted file mode 100644
index 8c000a1..0000000
--- a/cmds/installd/view_compiler.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2019 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 "view_compiler.h"
-
-#include <string>
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "utils.h"
-
-#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-#include "android-base/unique_fd.h"
-
-namespace android {
-namespace installd {
-
-namespace {
-
-using ::android::base::unique_fd;
-
-constexpr int kTimeoutMs = 300000;
-
-} // namespace
-
-bool view_compiler(const char* apk_path, const char* package_name, const char* out_dex_file,
-                   int uid) {
-    CHECK(apk_path != nullptr);
-    CHECK(package_name != nullptr);
-    CHECK(out_dex_file != nullptr);
-
-    // viewcompiler won't have permission to open anything, so we have to open the files first
-    // and pass file descriptors.
-
-    // Open input file
-    unique_fd infd{open(apk_path, O_RDONLY)}; // NOLINT(android-cloexec-open)
-    if (infd.get() < 0) {
-        PLOG(ERROR) << "Could not open input file: " << apk_path;
-        return false;
-    }
-
-    // Set up output file. viewcompiler can't open outputs by fd, but it can write to stdout, so
-    // we close stdout and open it towards the right output.
-    unique_fd outfd{open(out_dex_file, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644)};
-    if (outfd.get() < 0) {
-        PLOG(ERROR) << "Could not open output file: " << out_dex_file;
-        return false;
-    }
-    if (fchmod(outfd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
-        PLOG(ERROR) << "Could not change output file permissions";
-        return false;
-    }
-    if (dup2(outfd, STDOUT_FILENO) < 0) {
-        PLOG(ERROR) << "Could not duplicate output file descriptor";
-        return false;
-    }
-
-    // Prepare command line arguments for viewcompiler
-    std::string args[] = {"/system/bin/viewcompiler",
-                          "--apk",
-                          "--infd",
-                          android::base::StringPrintf("%d", infd.get()),
-                          "--dex",
-                          "--package",
-                          package_name};
-    char* const argv[] = {const_cast<char*>(args[0].c_str()), const_cast<char*>(args[1].c_str()),
-                          const_cast<char*>(args[2].c_str()), const_cast<char*>(args[3].c_str()),
-                          const_cast<char*>(args[4].c_str()), const_cast<char*>(args[5].c_str()),
-                          const_cast<char*>(args[6].c_str()), nullptr};
-
-    pid_t pid = fork();
-    if (pid == 0) {
-        // Now that we've opened the files we need, drop privileges.
-        drop_capabilities(uid);
-        execv("/system/bin/viewcompiler", argv);
-        _exit(1);
-    }
-
-    int return_code = wait_child_with_timeout(pid, kTimeoutMs);
-    if (!WIFEXITED(return_code)) {
-        LOG(WARNING) << "viewcompiler failed for " << package_name << ":" << apk_path;
-        if (WTERMSIG(return_code) == SIGKILL) {
-            // If the subprocess is killed while it's writing to the file, the file is likely
-            // corrupted, so we should remove it.
-            remove_file_at_fd(outfd.get());
-        }
-        return false;
-    }
-    return WEXITSTATUS(return_code) == 0;
-}
-
-} // namespace installd
-} // namespace android
diff --git a/cmds/installd/view_compiler.h b/cmds/installd/view_compiler.h
deleted file mode 100644
index aa141ca..0000000
--- a/cmds/installd/view_compiler.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#ifndef VIEW_COMPILER_H_
-#define VIEW_COMPILER_H_
-
-namespace android {
-namespace installd {
-
-bool view_compiler(const char* apk_path, const char* package_name, const char* out_dex_file,
-                   int uid);
-
-} // namespace installd
-} // namespace android
-
-#endif // VIEW_COMPILER_H_
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/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index e54f9d3..870e8eb 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -44,6 +44,7 @@
 #include "Timeout.h"
 #include "utils.h"
 
+using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
 using ::android::hidl::base::V1_0::DebugInfo;
@@ -522,27 +523,35 @@
     using namespace ::android::hardware;
     using namespace ::android::hidl::manager::V1_0;
     using namespace ::android::hidl::base::V1_0;
-    using std::literals::chrono_literals::operator""s;
-    auto ret = timeoutIPC(10s, manager, &IServiceManager::debugDump, [&] (const auto &infos) {
-        std::map<std::string, TableEntry> entries;
-        for (const auto &info : infos) {
-            std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" +
-                    std::string{info.instanceName.c_str()};
-            entries.emplace(interfaceName, TableEntry{
-                .interfaceName = interfaceName,
-                .transport = vintf::Transport::PASSTHROUGH,
-                .clientPids = info.clientPids,
-            }).first->second.arch |= fromBaseArchitecture(info.arch);
-        }
-        for (auto &&pair : entries) {
-            putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
-        }
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if ret.isOk(), the background thread has
+    // already ended, so it is safe to dereference entries.
+    auto entries = std::make_shared<std::map<std::string, TableEntry>>();
+    auto ret =
+            timeoutIPC(mLshal.getDebugDumpWait(), manager, &IServiceManager::debugDump,
+                       [entries](const auto& infos) {
+                           for (const auto& info : infos) {
+                               std::string interfaceName = std::string{info.interfaceName.c_str()} +
+                                       "/" + std::string{info.instanceName.c_str()};
+                               entries->emplace(interfaceName,
+                                                TableEntry{
+                                                        .interfaceName = interfaceName,
+                                                        .transport = vintf::Transport::PASSTHROUGH,
+                                                        .clientPids = info.clientPids,
+                                                })
+                                       .first->second.arch |= fromBaseArchitecture(info.arch);
+                           }
+                       });
     if (!ret.isOk()) {
         err() << "Error: Failed to call list on getPassthroughServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_ALL_LIBS_ERROR;
     }
+    for (auto&& pair : *entries) {
+        putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
+    }
     return OK;
 }
 
@@ -553,27 +562,40 @@
     using namespace ::android::hardware::details;
     using namespace ::android::hidl::manager::V1_0;
     using namespace ::android::hidl::base::V1_0;
-    auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) {
-        for (const auto &info : infos) {
-            if (info.clientPids.size() <= 0) {
-                continue;
-            }
-            putEntry(HalType::PASSTHROUGH_CLIENTS, {
-                .interfaceName =
-                        std::string{info.interfaceName.c_str()} + "/" +
-                        std::string{info.instanceName.c_str()},
-                .transport = vintf::Transport::PASSTHROUGH,
-                .serverPid = info.clientPids.size() == 1 ? info.clientPids[0] : NO_PID,
-                .clientPids = info.clientPids,
-                .arch = fromBaseArchitecture(info.arch)
-            });
-        }
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if ret.isOk(), the background thread has
+    // already ended, so it is safe to dereference entries.
+    auto entries = std::make_shared<std::vector<TableEntry>>();
+    auto ret =
+            timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::debugDump,
+                       [entries](const auto& infos) {
+                           for (const auto& info : infos) {
+                               if (info.clientPids.size() <= 0) {
+                                   continue;
+                               }
+                               entries->emplace_back(
+                                       TableEntry{.interfaceName =
+                                                          std::string{info.interfaceName.c_str()} +
+                                                          "/" +
+                                                          std::string{info.instanceName.c_str()},
+                                                  .transport = vintf::Transport::PASSTHROUGH,
+                                                  .serverPid = info.clientPids.size() == 1
+                                                          ? info.clientPids[0]
+                                                          : NO_PID,
+                                                  .clientPids = info.clientPids,
+                                                  .arch = fromBaseArchitecture(info.arch)});
+                           }
+                       });
     if (!ret.isOk()) {
         err() << "Error: Failed to call debugDump on defaultServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_PASSTHROUGH_ERROR;
     }
+    for (auto&& entry : *entries) {
+        putEntry(HalType::PASSTHROUGH_CLIENTS, std::move(entry));
+    }
     return OK;
 }
 
@@ -583,11 +605,14 @@
     if (!shouldFetchHalType(HalType::BINDERIZED_SERVICES)) { return OK; }
 
     const vintf::Transport mode = vintf::Transport::HWBINDER;
-    hidl_vec<hidl_string> fqInstanceNames;
-    // copying out for timeoutIPC
-    auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) {
-        fqInstanceNames = names;
-    });
+
+    // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+    // even though the interface function call is synchronous.
+    // However, there's no need to lock because if listRet.isOk(), the background thread has
+    // already ended, so it is safe to dereference fqInstanceNames.
+    auto fqInstanceNames = std::make_shared<hidl_vec<hidl_string>>();
+    auto listRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::list,
+                              [fqInstanceNames](const auto& names) { *fqInstanceNames = names; });
     if (!listRet.isOk()) {
         err() << "Error: Failed to list services for " << mode << ": "
              << listRet.description() << std::endl;
@@ -596,7 +621,7 @@
 
     Status status = OK;
     std::map<std::string, TableEntry> allTableEntries;
-    for (const auto &fqInstanceName : fqInstanceNames) {
+    for (const auto& fqInstanceName : *fqInstanceNames) {
         // create entry and default assign all fields.
         TableEntry& entry = allTableEntries[fqInstanceName];
         entry.interfaceName = fqInstanceName;
@@ -623,7 +648,8 @@
     const auto pair = splitFirst(entry->interfaceName, '/');
     const auto &serviceName = pair.first;
     const auto &instanceName = pair.second;
-    auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
+    auto getRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::get, serviceName,
+                             instanceName);
     if (!getRet.isOk()) {
         handleError(TRANSACTION_ERROR,
                     "cannot be fetched from service manager:" + getRet.description());
@@ -637,30 +663,33 @@
 
     // getDebugInfo
     do {
-        DebugInfo debugInfo;
-        auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &received) {
-            debugInfo = received;
-        });
+        // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+        // even though the interface function call is synchronous.
+        // However, there's no need to lock because if debugRet.isOk(), the background thread has
+        // already ended, so it is safe to dereference debugInfo.
+        auto debugInfo = std::make_shared<DebugInfo>();
+        auto debugRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getDebugInfo,
+                                   [debugInfo](const auto& received) { *debugInfo = received; });
         if (!debugRet.isOk()) {
             handleError(TRANSACTION_ERROR,
                         "debugging information cannot be retrieved: " + debugRet.description());
             break; // skip getPidInfo
         }
 
-        entry->serverPid = debugInfo.pid;
-        entry->serverObjectAddress = debugInfo.ptr;
-        entry->arch = fromBaseArchitecture(debugInfo.arch);
+        entry->serverPid = debugInfo->pid;
+        entry->serverObjectAddress = debugInfo->ptr;
+        entry->arch = fromBaseArchitecture(debugInfo->arch);
 
-        if (debugInfo.pid != NO_PID) {
-            const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+        if (debugInfo->pid != NO_PID) {
+            const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo->pid);
             if (pidInfo == nullptr) {
                 handleError(IO_ERROR,
-                            "no information for PID " + std::to_string(debugInfo.pid) +
-                            ", are you root?");
+                            "no information for PID " + std::to_string(debugInfo->pid) +
+                                    ", are you root?");
                 break;
             }
-            if (debugInfo.ptr != NO_PTR) {
-                auto it = pidInfo->refPids.find(debugInfo.ptr);
+            if (debugInfo->ptr != NO_PTR) {
+                auto it = pidInfo->refPids.find(debugInfo->ptr);
                 if (it != pidInfo->refPids.end()) {
                     entry->clientPids = it->second;
                 }
@@ -672,39 +701,46 @@
 
     // hash
     do {
-        ssize_t hashIndex = -1;
-        auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
-            for (size_t i = 0; i < c.size(); ++i) {
-                if (serviceName == c[i]) {
-                    hashIndex = static_cast<ssize_t>(i);
-                    break;
-                }
-            }
-        });
+        // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+        // even though the interface function call is synchronous.
+        auto hashIndexStore = std::make_shared<ssize_t>(-1);
+        auto ifaceChainRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::interfaceChain,
+                                        [hashIndexStore, serviceName](const auto& c) {
+                                            for (size_t i = 0; i < c.size(); ++i) {
+                                                if (serviceName == c[i]) {
+                                                    *hashIndexStore = static_cast<ssize_t>(i);
+                                                    break;
+                                                }
+                                            }
+                                        });
         if (!ifaceChainRet.isOk()) {
             handleError(TRANSACTION_ERROR,
                         "interfaceChain fails: " + ifaceChainRet.description());
             break; // skip getHashChain
         }
+        // if ifaceChainRet.isOk(), the background thread has already ended, so it is safe to
+        // dereference hashIndex without any locking.
+        auto hashIndex = *hashIndexStore;
         if (hashIndex < 0) {
             handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
             break; // skip getHashChain
         }
-        auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
-            if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
-                handleError(BAD_IMPL,
-                            "interfaceChain indicates position " + std::to_string(hashIndex) +
-                            " but getHashChain returns " + std::to_string(hashChain.size()) +
-                            " hashes");
-                return;
-            }
-
-            auto&& hashArray = hashChain[hashIndex];
-            entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
-        });
+        // See comments about hashIndex above.
+        auto hashChain = std::make_shared<hidl_vec<hidl_array<uint8_t, 32>>>();
+        auto hashRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getHashChain,
+                                  [hashChain](const auto& ret) { *hashChain = std::move(ret); });
         if (!hashRet.isOk()) {
             handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
         }
+        if (static_cast<size_t>(hashIndex) >= hashChain->size()) {
+            handleError(BAD_IMPL,
+                        "interfaceChain indicates position " + std::to_string(hashIndex) +
+                                " but getHashChain returns " + std::to_string(hashChain->size()) +
+                                " hashes");
+        } else {
+            auto&& hashArray = (*hashChain)[hashIndex];
+            entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
+        }
     } while (0);
     if (status == OK) {
         entry->serviceStatus = ServiceStatus::ALIVE;
diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp
index a5f98c2..6115da7 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -250,5 +250,17 @@
     return mPassthroughManager;
 }
 
+void Lshal::setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+                               std::chrono::milliseconds debugDumpWait) {
+    mIpcCallWait = ipcCallWait;
+    mDebugDumpWait = debugDumpWait;
+}
+std::chrono::milliseconds Lshal::getIpcCallWait() const {
+    return mIpcCallWait;
+}
+std::chrono::milliseconds Lshal::getDebugDumpWait() const {
+    return mDebugDumpWait;
+}
+
 }  // namespace lshal
 }  // namespace android
diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h
index 50279d4..cb2820c 100644
--- a/cmds/lshal/Lshal.h
+++ b/cmds/lshal/Lshal.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <iostream>
 #include <string>
 
@@ -58,6 +59,11 @@
 
     void forEachCommand(const std::function<void(const Command* c)>& f) const;
 
+    void setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+                            std::chrono::milliseconds debugDumpWait);
+    std::chrono::milliseconds getIpcCallWait() const;
+    std::chrono::milliseconds getDebugDumpWait() const;
+
 private:
     Status parseArgs(const Arg &arg);
 
@@ -70,6 +76,9 @@
 
     std::vector<std::unique_ptr<Command>> mRegisteredCommands;
 
+    std::chrono::milliseconds mIpcCallWait{500};
+    std::chrono::milliseconds mDebugDumpWait{10000};
+
     DISALLOW_COPY_AND_ASSIGN(Lshal);
 };
 
diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h
index e8d22d9..805e8dc 100644
--- a/cmds/lshal/Timeout.h
+++ b/cmds/lshal/Timeout.h
@@ -27,8 +27,6 @@
 namespace android {
 namespace lshal {
 
-static constexpr std::chrono::milliseconds IPC_CALL_WAIT{500};
-
 class BackgroundTaskState {
 public:
     explicit BackgroundTaskState(std::function<void(void)> &&func)
@@ -96,12 +94,5 @@
     return ret;
 }
 
-template<class Function, class I, class... Args>
-typename std::result_of<Function(I *, Args...)>::type
-timeoutIPC(const sp<I> &interfaceObject, Function &&func, Args &&... args) {
-    return timeoutIPC(IPC_CALL_WAIT, interfaceObject, func, args...);
-}
-
-
 }  // namespace lshal
 }  // namespace android
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
index 55326ea..de0e171 100644
--- a/cmds/sfdo/sfdo.cpp
+++ b/cmds/sfdo/sfdo.cpp
@@ -16,7 +16,7 @@
 #include <inttypes.h>
 #include <stdint.h>
 #include <any>
-#include <unordered_map>
+#include <map>
 
 #include <cutils/properties.h>
 #include <sys/resource.h>
@@ -29,18 +29,28 @@
 
 using namespace android;
 
-std::unordered_map<std::string, std::any> g_functions;
+std::map<std::string, std::any> g_functions;
 
-const std::unordered_map<std::string, std::string> g_function_details = {
-    {"DebugFlash", "[optional(delay)] Perform a debug flash."},
-    {"FrameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
-    {"scheduleComposite", "Force composite ahead of next VSYNC."},
-    {"scheduleCommit", "Force commit ahead of next VSYNC."},
-    {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+enum class ParseToggleResult {
+    kError,
+    kFalse,
+    kTrue,
+};
+
+const std::map<std::string, std::string> g_function_details = {
+        {"debugFlash", "[optional(delay)] Perform a debug flash."},
+        {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
+        {"scheduleComposite", "Force composite ahead of next VSYNC."},
+        {"scheduleCommit", "Force commit ahead of next VSYNC."},
+        {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+        {"forceClientComposition",
+         "[enabled | disabled] When enabled, it disables "
+         "Hardware Overlays, and routes all window composition to the GPU. This can "
+         "help check if there is a bug in HW Composer."},
 };
 
 static void ShowUsage() {
-    std::cout << "usage: sfdo [help, FrameRateIndicator show, DebugFlash enabled, ...]\n\n";
+    std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
     for (const auto& sf : g_functions) {
         const std::string fn = sf.first;
         std::string fdetails = "TODO";
@@ -50,7 +60,26 @@
     }
 }
 
-int FrameRateIndicator([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+// Returns 1 for positive keywords and 0 for negative keywords.
+// If the string does not match any it will return -1.
+ParseToggleResult parseToggle(const char* str) {
+    const std::unordered_set<std::string> positive{"1",  "true",    "y",   "yes",
+                                                   "on", "enabled", "show"};
+    const std::unordered_set<std::string> negative{"0",   "false",    "n",   "no",
+                                                   "off", "disabled", "hide"};
+
+    const std::string word(str);
+    if (positive.count(word)) {
+        return ParseToggleResult::kTrue;
+    }
+    if (negative.count(word)) {
+        return ParseToggleResult::kFalse;
+    }
+
+    return ParseToggleResult::kError;
+}
+
+int frameRateIndicator(int argc, char** argv) {
     bool hide = false, show = false;
     if (argc == 3) {
         show = strcmp(argv[2], "show") == 0;
@@ -60,13 +89,13 @@
     if (show || hide) {
         ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
     } else {
-        std::cerr << "Incorrect usage of FrameRateIndicator. Missing [hide | show].\n";
+        std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
         return -1;
     }
     return 0;
 }
 
-int DebugFlash([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+int debugFlash(int argc, char** argv) {
     int delay = 0;
     if (argc == 3) {
         delay = atoi(argv[2]) == 0;
@@ -86,14 +115,40 @@
     return 0;
 }
 
+int forceClientComposition(int argc, char** argv) {
+    bool enabled = true;
+    // A valid command looks like this:
+    // adb shell sfdo forceClientComposition enabled
+    if (argc >= 3) {
+        const ParseToggleResult toggle = parseToggle(argv[2]);
+        if (toggle == ParseToggleResult::kError) {
+            std::cerr << "Incorrect usage of forceClientComposition. "
+                         "Missing [enabled | disabled].\n";
+            return -1;
+        }
+        if (argc > 3) {
+            std::cerr << "Too many arguments after [enabled | disabled]. "
+                         "Ignoring extra arguments.\n";
+        }
+        enabled = (toggle == ParseToggleResult::kTrue);
+    } else {
+        std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
+        return -1;
+    }
+
+    ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
+    return 0;
+}
+
 int main(int argc, char** argv) {
     std::cout << "Execute SurfaceFlinger internal commands.\n";
     std::cout << "sfdo requires to be run with root permissions..\n";
 
-    g_functions["FrameRateIndicator"] = FrameRateIndicator;
-    g_functions["DebugFlash"] = DebugFlash;
+    g_functions["frameRateIndicator"] = frameRateIndicator;
+    g_functions["debugFlash"] = debugFlash;
     g_functions["scheduleComposite"] = scheduleComposite;
     g_functions["scheduleCommit"] = scheduleCommit;
+    g_functions["forceClientComposition"] = forceClientComposition;
 
     if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
         std::cout << "Running: " << argv[1] << "\n";
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index e286a84..89736ec 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -131,6 +131,24 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.se.omapi.ese.prebuilt.xml",
+    src: "android.hardware.se.omapi.ese.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.se.omapi.sd.prebuilt.xml",
+    src: "android.hardware.se.omapi.sd.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+    name: "android.hardware.se.omapi.uicc.prebuilt.xml",
+    src: "android.hardware.se.omapi.uicc.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.prebuilt.xml",
     src: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -353,6 +371,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.opengles.deqp.level-2024-03-01.prebuilt.xml",
+    src: "android.software.opengles.deqp.level-2024-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.opengles.deqp.level-latest.prebuilt.xml",
     src: "android.software.opengles.deqp.level-latest.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -389,6 +413,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.vulkan.deqp.level-2024-03-01.prebuilt.xml",
+    src: "android.software.vulkan.deqp.level-2024-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.vulkan.deqp.level-latest.prebuilt.xml",
     src: "android.software.vulkan.deqp.level-latest.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.software.opengles.deqp.level-2024-03-01.xml b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000..4eeed2a
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+     dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+    <feature name="android.software.opengles.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-latest.xml b/data/etc/android.software.opengles.deqp.level-latest.xml
index bd15eb6..62bb101 100644
--- a/data/etc/android.software.opengles.deqp.level-latest.xml
+++ b/data/etc/android.software.opengles.deqp.level-latest.xml
@@ -17,5 +17,5 @@
 <!-- This is the standard feature indicating that the device passes OpenGL ES
      dEQP tests associated with the most recent level for this Android version. -->
 <permissions>
-    <feature name="android.software.opengles.deqp.level" version="132580097" />
+    <feature name="android.software.opengles.deqp.level" version="132645633" />
 </permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000..8b2b4c8
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+     tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+    <feature name="android.software.vulkan.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-latest.xml b/data/etc/android.software.vulkan.deqp.level-latest.xml
index 87be070..0fc12b3 100644
--- a/data/etc/android.software.vulkan.deqp.level-latest.xml
+++ b/data/etc/android.software.vulkan.deqp.level-latest.xml
@@ -17,5 +17,5 @@
 <!-- This is the standard feature indicating that the device passes Vulkan
      dEQP tests associated with the most recent level for this Android version. -->
 <permissions>
-    <feature name="android.software.vulkan.deqp.level" version="132580097" />
+    <feature name="android.software.vulkan.deqp.level" version="132645633" />
 </permissions>
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/android/thermal.h b/include/android/thermal.h
index 1f477f8..0b57e93 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@
  * It's passed the updated thermal status as parameter, as well as the
  * pointer provided by the client that registered a callback.
  */
-typedef void (*AThermal_StatusCallback)(void *data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
 
 /**
   * Acquire an instance of the thermal manager. This must be freed using
@@ -222,6 +222,70 @@
 float AThermal_getThermalHeadroom(AThermalManager *manager,
         int forecastSeconds) __INTRODUCED_IN(31);
 
+/**
+ * This struct defines an instance of headroom threshold value and its status.
+ * <p>
+ * The value should be monotonically non-decreasing as the thermal status increases.
+ * For {@link ATHERMAL_STATUS_SEVERE}, its headroom threshold is guaranteed to
+ * be 1.0f. For status below severe status, the value should be lower or equal
+ * to 1.0f, and for status above severe, the value should be larger or equal to 1.0f.
+ * <p>
+ * Also see {@link AThermal_getThermalHeadroom} for explanation on headroom, and
+ * {@link AThermal_getThermalHeadroomThresholds} for how to use this.
+ */
+struct AThermalHeadroomThreshold {
+    float headroom;
+    AThermalStatus thermalStatus;
+};
+
+/**
+ * Gets the thermal headroom thresholds for all available thermal status.
+ *
+ * A thermal status will only exist in output if the device manufacturer has the
+ * corresponding threshold defined for at least one of its slow-moving skin temperature
+ * sensors. If it's set, one should also expect to get it from
+ * {@link #AThermal_getCurrentThermalStatus} or {@link AThermal_StatusCallback}.
+ * <p>
+ * The headroom threshold is used to interpret the possible thermal throttling status based on
+ * the headroom prediction. For example, if the headroom threshold for
+ * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
+ * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * could be in lightly throttled state if the workload remains the same. The app can consider
+ * taking actions according to the nearest throttling status the difference between the headroom and
+ * the threshold.
+ * <p>
+ * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
+ * sensors reporting different threshold values, the minimum threshold is taken to be conservative
+ * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
+ * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * will always come with lightly throttled state
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * While it's always guaranteed that the device won't be throttled heavier than the unmet
+ * threshold's state, so a real-time headroom of 0.75 will never come with
+ * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
+ * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
+ * <p>
+ * The returned list of thresholds is cached on first successful query and owned by the thermal
+ * manager, which will not change between calls to this function. The caller should only need to
+ * free the manager with {@link AThermal_releaseManager}.
+ *
+ * @param manager The manager instance to use.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
+ *                will be set to the cached array of thresholds if thermal thresholds are supported
+ *                by the system or device, otherwise nullptr or unmodified.
+ * @param size non-null output pointer whose value will be set to the size of the threshold array
+ *             or 0 if it's not supported.
+ * @return 0 on success
+ *         EINVAL if outThresholds or size_t is nullptr, or *outThresholds is not nullptr.
+ *         EPIPE if communication with the system service has failed.
+ *         ENOSYS if the feature is disabled by the current system.
+ */
+int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
+                                          const AThermalHeadroomThreshold ** outThresholds,
+                                          size_t* size) __INTRODUCED_IN(35);
+
 #ifdef __cplusplus
 }
 #endif
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/ftl/details/function.h b/include/ftl/details/function.h
new file mode 100644
index 0000000..35c5a8b
--- /dev/null
+++ b/include/ftl/details/function.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 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
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace android::ftl::details {
+
+// The maximum allowed value for the template argument `N` in
+// `ftl::Function<F, N>`.
+constexpr size_t kFunctionMaximumN = 14;
+
+// Converts a member function pointer type `Ret(Class::*)(Args...)` to an equivalent non-member
+// function type `Ret(Args...)`.
+
+template <typename>
+struct remove_member_function_pointer;
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...)> {
+  using type = Ret(Args...);
+};
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...) const> {
+  using type = Ret(Args...);
+};
+
+template <auto MemberFunction>
+using remove_member_function_pointer_t =
+    typename remove_member_function_pointer<decltype(MemberFunction)>::type;
+
+// Helper functions for binding to the supported targets.
+
+template <typename Ret, typename... Args>
+auto bind_opaque_no_op() -> Ret (*)(void*, Args...) {
+  return [](void*, Args...) -> Ret {
+    if constexpr (!std::is_void_v<Ret>) {
+      return Ret{};
+    }
+  };
+}
+
+template <typename F, typename Ret, typename... Args>
+auto bind_opaque_function_object(const F&) -> Ret (*)(void*, Args...) {
+  return [](void* opaque, Args... args) -> Ret {
+    return std::invoke(*static_cast<F*>(opaque), std::forward<Args>(args)...);
+  };
+}
+
+template <auto MemberFunction, typename Class, typename Ret, typename... Args>
+auto bind_member_function(Class* instance, Ret (*)(Args...) = nullptr) {
+  return [instance](Args... args) -> Ret {
+    return std::invoke(MemberFunction, instance, std::forward<Args>(args)...);
+  };
+}
+
+template <auto FreeFunction, typename Ret, typename... Args>
+auto bind_free_function(Ret (*)(Args...) = nullptr) {
+  return [](Args... args) -> Ret { return std::invoke(FreeFunction, std::forward<Args>(args)...); };
+}
+
+// Traits class for the opaque storage used by Function.
+
+template <std::size_t N>
+struct function_opaque_storage {
+  // The actual type used for the opaque storage. An `N` of zero specifies the minimum useful size,
+  // which allows a lambda with zero or one capture args.
+  using type = std::array<std::intptr_t, N + 1>;
+
+  template <typename S>
+  static constexpr bool require_trivially_copyable = std::is_trivially_copyable_v<S>;
+
+  template <typename S>
+  static constexpr bool require_trivially_destructible = std::is_trivially_destructible_v<S>;
+
+  template <typename S>
+  static constexpr bool require_will_fit_in_opaque_storage = sizeof(S) <= sizeof(type);
+
+  template <typename S>
+  static constexpr bool require_alignment_compatible =
+      std::alignment_of_v<S> <= std::alignment_of_v<type>;
+
+  // Copies `src` into the opaque storage, and returns that storage.
+  template <typename S>
+  static type opaque_copy(const S& src) {
+    // TODO: Replace with C++20 concepts/constraints which can give more details.
+    static_assert(require_trivially_copyable<S>,
+                  "ftl::Function can only store lambdas that capture trivially copyable data.");
+    static_assert(
+        require_trivially_destructible<S>,
+        "ftl::Function can only store lambdas that capture trivially destructible data.");
+    static_assert(require_will_fit_in_opaque_storage<S>,
+                  "ftl::Function has limited storage for lambda captured state. Maybe you need to "
+                  "increase N?");
+    static_assert(require_alignment_compatible<S>);
+
+    type opaque;
+    std::memcpy(opaque.data(), &src, sizeof(S));
+    return opaque;
+  }
+};
+
+// Traits class to help determine the template parameters to use for a ftl::Function, given a
+// function object.
+
+template <typename F, typename = decltype(&F::operator())>
+struct function_traits {
+  // The function type `F` with which to instantiate the `Function<F, N>` template.
+  using type = remove_member_function_pointer_t<&F::operator()>;
+
+  // The (minimum) size `N` with which to instantiate the `Function<F, N>` template.
+  static constexpr std::size_t size =
+      (std::max(sizeof(std::intptr_t), sizeof(F)) - 1) / sizeof(std::intptr_t);
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/function.h b/include/ftl/function.h
new file mode 100644
index 0000000..3538ca4
--- /dev/null
+++ b/include/ftl/function.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright 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
+
+#include <cstddef>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/details/function.h>
+
+namespace android::ftl {
+
+// ftl::Function<F, N> is a container for function object, and can mostly be used in place of
+// std::function<F>.
+//
+// Unlike std::function<F>, a ftl::Function<F, N>:
+//
+//  * Uses a static amount of memory (controlled by N), and never any dynamic allocation.
+//  * Satisfies the std::is_trivially_copyable<> trait.
+//  * Satisfies the std::is_trivially_destructible<> trait.
+//
+// However those same limits are also required from the contained function object in turn.
+//
+// The size of a ftl::Function<F, N> is guaranteed to be:
+//
+//     sizeof(std::intptr_t) * (N + 2)
+//
+// A ftl::Function<F, N> can always be implicitly converted to a larger size ftl::Function<F, M>.
+// Trying to convert the other way leads to a compilation error.
+//
+// A default-constructed ftl::Function is in an empty state. The operator bool() overload returns
+// false in this state. It is undefined behavior to attempt to invoke the function in this state.
+//
+// The ftl::Function<F, N> can also be constructed or assigned from ftl::no_op. This sets up the
+// ftl::Function to be non-empty, with a function that when called does nothing except
+// default-constructs a return value.
+//
+// The ftl::make_function() helpers construct a ftl::Function<F, N>, including deducing the
+// values of F and N from the arguments it is given.
+//
+// The static ftl::Function<F, N>::make() helpers construct a ftl::Function<F, N> without that
+// deduction, and also allow for implicit argument conversion if the target being called needs them.
+//
+// The construction helpers allow any of the following types of functions to be stored:
+//
+//  * Any SMALL function object (as defined by the C++ Standard), such as a lambda with a small
+//    capture, or other "functor". The requirements are:
+//
+//      1) The function object must be trivial to destroy (in fact, the destructor will never
+//         actually be called once copied to the internal storage).
+//      2) The function object must be trivial to copy (the raw bytes will be copied as the
+//         ftl::Function<F, N> is copied/moved).
+//      3) The size of the function object cannot be larger than sizeof(std::intptr_t) * (N + 1),
+//         and it cannot require stricter alignment than alignof(std::intptr_t).
+//
+//    With the default of N=0, a lambda can only capture a single pointer-sized argument. This is
+//    enough to capture `this`, which is why N=0 is the default.
+//
+//  * A member function, with the address passed as the template value argument to the construction
+//    helper function, along with the instance pointer needed to invoke it passed as an ordinary
+//    argument.
+//
+//        ftl::make_function<&Class::member_function>(this);
+//
+//    Note that the indicated member function will be invoked non-virtually. If you need it to be
+//    invoked virtually, you should invoke it yourself with a small lambda like so:
+//
+//        ftl::function([this] { virtual_member_function(); });
+//
+//  * An ordinary function ("free function"), with the address of the function passed as a template
+//    value argument.
+//
+//        ftl::make_function<&std::atoi>();
+//
+//   As with the member function helper, as the function is known at compile time, it will be called
+//   directly.
+//
+// Example usage:
+//
+//   class MyClass {
+//    public:
+//     void on_event() const {}
+//     int on_string(int*, std::string_view) { return 1; }
+//
+//     auto get_function() {
+//       return ftl::function([this] { on_event(); });
+//     }
+//   } cls;
+//
+//   // A function container with no arguments, and returning no value.
+//   ftl::Function<void()> f;
+//
+//   // Construct a ftl::Function containing a small lambda.
+//   f = cls.get_function();
+//
+//   // Construct a ftl::Function that calls `cls.on_event()`.
+//   f = ftl::function<&MyClass::on_event>(&cls);
+//
+//   // Create a do-nothing function.
+//   f = ftl::no_op;
+//
+//   // Invoke the contained function.
+//   f();
+//
+//   // Also invokes it.
+//   std::invoke(f);
+//
+//   // Create a typedef to give a more meaningful name and bound the size.
+//   using MyFunction = ftl::Function<int(std::string_view), 2>;
+//   int* ptr = nullptr;
+//   auto f1 = MyFunction::make_function(
+//       [cls = &cls, ptr](std::string_view sv) {
+//           return cls->on_string(ptr, sv);
+//       });
+//   int r = f1("abc"sv);
+//
+//   // Returns a default-constructed int (0).
+//   f1 = ftl::no_op;
+//   r = f1("abc"sv);
+//   assert(r == 0);
+
+template <typename F, std::size_t N = 0>
+class Function;
+
+// Used to construct a Function that does nothing.
+struct NoOpTag {};
+
+constexpr NoOpTag no_op;
+
+// Detects that a type is a `ftl::Function<F, N>` regardless of what `F` and `N` are.
+template <typename>
+struct is_function : public std::false_type {};
+
+template <typename F, std::size_t N>
+struct is_function<Function<F, N>> : public std::true_type {};
+
+template <typename T>
+constexpr bool is_function_v = is_function<T>::value;
+
+template <typename Ret, typename... Args, std::size_t N>
+class Function<Ret(Args...), N> final {
+  // Enforce a valid size, with an arbitrary maximum allowed size for the container of
+  // sizeof(std::intptr_t) * 16, though that maximum can be relaxed.
+  static_assert(N <= details::kFunctionMaximumN);
+
+  using OpaqueStorageTraits = details::function_opaque_storage<N>;
+
+ public:
+  // Defining result_type allows ftl::Function to be substituted for std::function.
+  using result_type = Ret;
+
+  // Constructs an empty ftl::Function.
+  Function() = default;
+
+  // Constructing or assigning from nullptr_t also creates an empty ftl::Function.
+  Function(std::nullptr_t) {}
+  Function& operator=(std::nullptr_t) { return *this = Function(nullptr); }
+
+  // Constructing from NoOpTag sets up a a special no-op function which is valid to call, and which
+  // returns a default constructed return value.
+  Function(NoOpTag) : function_(details::bind_opaque_no_op<Ret, Args...>()) {}
+  Function& operator=(NoOpTag) { return *this = Function(no_op); }
+
+  // Constructing/assigning from a function object stores a copy of that function object, however:
+  //  * It must be trivially copyable, as the implementation makes a copy with memcpy().
+  //  * It must be trivially destructible, as the implementation doesn't destroy the copy!
+  //  * It must fit in the limited internal storage, which enforces size/alignment restrictions.
+
+  template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+  Function(const F& f)
+      : opaque_(OpaqueStorageTraits::opaque_copy(f)),
+        function_(details::bind_opaque_function_object<F, Ret, Args...>(f)) {}
+
+  template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+  Function& operator=(const F& f) noexcept {
+    return *this = Function{OpaqueStorageTraits::opaque_copy(f),
+                            details::bind_opaque_function_object<F, Ret, Args...>(f)};
+  }
+
+  // Constructing/assigning from a smaller ftl::Function is allowed, but not anything else.
+
+  template <std::size_t M>
+  Function(const Function<Ret(Args...), M>& other)
+      : opaque_{OpaqueStorageTraits::opaque_copy(other.opaque_)}, function_(other.function_) {}
+
+  template <std::size_t M>
+  auto& operator=(const Function<Ret(Args...), M>& other) {
+    return *this = Function{OpaqueStorageTraits::opaque_copy(other.opaque_), other.function_};
+  }
+
+  // Returns true if a function is set.
+  explicit operator bool() const { return function_ != nullptr; }
+
+  // Checks if the other function has the same contents as this one.
+  bool operator==(const Function& other) const {
+    return other.opaque_ == opaque_ && other.function_ == function_;
+  }
+  bool operator!=(const Function& other) const { return !operator==(other); }
+
+  // Alternative way of testing for a function being set.
+  bool operator==(std::nullptr_t) const { return function_ == nullptr; }
+  bool operator!=(std::nullptr_t) const { return function_ != nullptr; }
+
+  // Invokes the function.
+  Ret operator()(Args... args) const {
+    return std::invoke(function_, opaque_.data(), std::forward<Args>(args)...);
+  }
+
+  // Creation helper for function objects, such as lambdas.
+  template <typename F>
+  static auto make(const F& f) -> decltype(Function{f}) {
+    return Function{f};
+  }
+
+  // Creation helper for a class pointer and a compile-time chosen member function to call.
+  template <auto MemberFunction, typename Class>
+  static auto make(Class* instance) -> decltype(Function{
+      details::bind_member_function<MemberFunction>(instance,
+                                                    static_cast<Ret (*)(Args...)>(nullptr))}) {
+    return Function{details::bind_member_function<MemberFunction>(
+        instance, static_cast<Ret (*)(Args...)>(nullptr))};
+  }
+
+  // Creation helper for a compile-time chosen free function to call.
+  template <auto FreeFunction>
+  static auto make() -> decltype(Function{
+      details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))}) {
+    return Function{
+        details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))};
+  }
+
+ private:
+  // Needed so a Function<F, M> can be converted to a Function<F, N>.
+  template <typename, std::size_t>
+  friend class Function;
+
+  // The function pointer type of function stored in `function_`. The first argument is always
+  // `&opaque_`.
+  using StoredFunction = Ret(void*, Args...);
+
+  // The type of the opaque storage, used to hold an appropriate function object.
+  // The type stored here is ONLY known to the StoredFunction.
+  // We always use at least one std::intptr_t worth of storage, and always a multiple of that size.
+  using OpaqueStorage = typename OpaqueStorageTraits::type;
+
+  // Internal constructor for creating from a raw opaque blob + function pointer.
+  Function(const OpaqueStorage& opaque, StoredFunction* function)
+      : opaque_(opaque), function_(function) {}
+
+  // Note: `mutable` so that `operator() const` can use it.
+  mutable OpaqueStorage opaque_{};
+  StoredFunction* function_{nullptr};
+};
+
+// Makes a ftl::Function given a function object `F`.
+template <typename F, typename T = details::function_traits<F>>
+Function(const F&) -> Function<typename T::type, T::size>;
+
+template <typename F>
+auto make_function(const F& f) -> decltype(Function{f}) {
+  return Function{f};
+}
+
+// Makes a ftl::Function given a `MemberFunction` and a instance pointer to the associated `Class`.
+template <auto MemberFunction, typename Class>
+auto make_function(Class* instance)
+    -> decltype(Function{details::bind_member_function<MemberFunction>(
+        instance,
+        static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))}) {
+  return Function{details::bind_member_function<MemberFunction>(
+      instance, static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))};
+}
+
+// Makes a ftl::Function given an ordinary free function.
+template <auto FreeFunction>
+auto make_function() -> decltype(Function{
+    details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))}) {
+  return Function{
+      details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))};
+}
+
+}  // namespace android::ftl
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index 7457496..b0eceef 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -130,9 +130,9 @@
                             "isActive=[%d]",
                             ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(),
                             physicalPort ? ftl::to_string(*physicalPort).c_str() : "<none>",
-                            orientation, logicalLeft, logicalTop, logicalRight, logicalBottom,
-                            physicalLeft, physicalTop, physicalRight, physicalBottom, deviceWidth,
-                            deviceHeight, isActive);
+                            static_cast<int>(orientation), logicalLeft, logicalTop, logicalRight,
+                            logicalBottom, physicalLeft, physicalTop, physicalRight, physicalBottom,
+                            deviceWidth, deviceHeight, isActive);
     }
 };
 
diff --git a/include/input/Input.h b/include/input/Input.h
index bd544b5..7b253a5 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -515,6 +515,8 @@
     PointerProperties& operator=(const PointerProperties&) = default;
 };
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties);
+
 // TODO(b/211379801) : Use a strong type from ftl/mixins.h instead
 using DeviceId = int32_t;
 
@@ -1138,6 +1140,24 @@
     std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEventPool;
 };
 
+/**
+ * An input event factory implementation that simply creates the input events on the heap, when
+ * needed. The caller is responsible for destroying the returned references.
+ * It is recommended that the caller wrap these return values into std::unique_ptr.
+ */
+class DynamicInputEventFactory : public InputEventFactoryInterface {
+public:
+    explicit DynamicInputEventFactory(){};
+    ~DynamicInputEventFactory(){};
+
+    KeyEvent* createKeyEvent() override { return new KeyEvent(); };
+    MotionEvent* createMotionEvent() override { return new MotionEvent(); };
+    FocusEvent* createFocusEvent() override { return new FocusEvent(); };
+    CaptureEvent* createCaptureEvent() override { return new CaptureEvent(); };
+    DragEvent* createDragEvent() override { return new DragEvent(); };
+    TouchModeEvent* createTouchModeEvent() override { return new TouchModeEvent(); };
+};
+
 /*
  * Describes a unique request to enable or disable Pointer Capture.
  */
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 9c0c10e..2d23b97 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -160,4 +160,90 @@
     std::vector<PointerBuilder> mPointers;
 };
 
+class KeyEventBuilder {
+public:
+    KeyEventBuilder(int32_t action, int32_t source) {
+        mAction = action;
+        mSource = source;
+        mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDownTime = mEventTime;
+    }
+
+    KeyEventBuilder(const KeyEvent& event) {
+        mAction = event.getAction();
+        mDeviceId = event.getDeviceId();
+        mSource = event.getSource();
+        mDownTime = event.getDownTime();
+        mEventTime = event.getEventTime();
+        mDisplayId = event.getDisplayId();
+        mFlags = event.getFlags();
+        mKeyCode = event.getKeyCode();
+        mScanCode = event.getScanCode();
+        mMetaState = event.getMetaState();
+        mRepeatCount = event.getRepeatCount();
+    }
+
+    KeyEventBuilder& deviceId(int32_t deviceId) {
+        mDeviceId = deviceId;
+        return *this;
+    }
+
+    KeyEventBuilder& downTime(nsecs_t downTime) {
+        mDownTime = downTime;
+        return *this;
+    }
+
+    KeyEventBuilder& eventTime(nsecs_t eventTime) {
+        mEventTime = eventTime;
+        return *this;
+    }
+
+    KeyEventBuilder& displayId(int32_t displayId) {
+        mDisplayId = displayId;
+        return *this;
+    }
+
+    KeyEventBuilder& policyFlags(int32_t policyFlags) {
+        mPolicyFlags = policyFlags;
+        return *this;
+    }
+
+    KeyEventBuilder& addFlag(uint32_t flags) {
+        mFlags |= flags;
+        return *this;
+    }
+
+    KeyEventBuilder& keyCode(int32_t keyCode) {
+        mKeyCode = keyCode;
+        return *this;
+    }
+
+    KeyEventBuilder& repeatCount(int32_t repeatCount) {
+        mRepeatCount = repeatCount;
+        return *this;
+    }
+
+    KeyEvent build() const {
+        KeyEvent event{};
+        event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
+                         mAction, mFlags, mKeyCode, mScanCode, mMetaState, mRepeatCount, mDownTime,
+                         mEventTime);
+        return event;
+    }
+
+private:
+    int32_t mAction;
+    int32_t mDeviceId = DEFAULT_DEVICE_ID;
+    uint32_t mSource;
+    nsecs_t mDownTime;
+    nsecs_t mEventTime;
+    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
+    int32_t mFlags{0};
+    int32_t mKeyCode{AKEYCODE_UNKNOWN};
+    int32_t mScanCode{0};
+    int32_t mMetaState{AMETA_NONE};
+    int32_t mRepeatCount{0};
+};
+
 } // namespace android
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index f20829c..750e170 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;
     /**
@@ -317,7 +317,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.
@@ -329,7 +329,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/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index b2e8baa..dfcf766 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -146,7 +146,7 @@
 
 #ifdef __linux__
     /* Reads a key map from a parcel. */
-    static std::shared_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
+    static std::unique_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
 
     /* Writes a key map to a parcel. */
     void writeToParcel(Parcel* parcel) const;
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/include/input/PrintTools.h b/include/input/PrintTools.h
index 63c0e40..3470be4 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -20,6 +20,7 @@
 #include <map>
 #include <optional>
 #include <set>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -33,6 +34,13 @@
     return bitset.to_string();
 }
 
+template <class T>
+std::string streamableToString(const T& streamable) {
+    std::stringstream out;
+    out << streamable;
+    return out.str();
+}
+
 template <typename T>
 inline std::string constToString(const T& v) {
     return std::to_string(v);
@@ -109,11 +117,12 @@
 template <typename T>
 std::string dumpVector(const std::vector<T>& values,
                        std::string (*valueToString)(const T&) = constToString) {
-    std::string dump = valueToString(values[0]);
-    for (size_t i = 1; i < values.size(); i++) {
-        dump += ", " + valueToString(values[i]);
+    std::string out;
+    for (const auto& value : values) {
+        out += out.empty() ? "[" : ", ";
+        out += valueToString(value);
     }
-    return dump;
+    return out.empty() ? "[]" : (out + "]");
 }
 
 const char* toString(bool value);
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 2e99495..ee74455 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -98,7 +98,7 @@
     void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position);
 
     // Adds movement information for all pointers in a MotionEvent, including historical samples.
-    void addMovement(const MotionEvent* event);
+    void addMovement(const MotionEvent& event);
 
     // Returns the velocity of the specified pointer id and axis in position units per second.
     // Returns empty optional if there is insufficient movement information for the pointer, or if
diff --git a/libs/gui/include/gui/Flags.h b/include/private/thermal_private.h
similarity index 63%
copy from libs/gui/include/gui/Flags.h
copy to include/private/thermal_private.h
index a2cff56..951d953 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/include/private/thermal_private.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -14,9 +14,18 @@
  * limitations under the License.
  */
 
-#pragma once
+#ifndef ANDROID_PRIVATE_NATIVE_THERMAL_H
+#define ANDROID_PRIVATE_NATIVE_THERMAL_H
 
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/**
+ * For testing only.
+ */
+void AThermal_setIThermalServiceForTesting(void* iThermalService);
+
+__END_DECLS
+
+#endif // ANDROID_PRIVATE_NATIVE_THERMAL_H
\ No newline at end of file
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 620c23c..ae0fb01 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -22,23 +22,51 @@
 }
 
 cc_library_headers {
-    name: "libbinder_headers",
+    name: "libbinder_headers_base",
     export_include_dirs: ["include"],
     vendor_available: true,
     recovery_available: true,
     host_supported: true,
-    // TODO(b/153609531): remove when no longer needed.
+    native_bridge_supported: true,
+
+    header_libs: [
+        "libbinder_headers_platform_shared",
+    ],
+    export_header_lib_headers: [
+        "libbinder_headers_platform_shared",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
+    min_sdk_version: "29",
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_headers {
+    name: "libbinder_headers",
+    vendor_available: true,
+    recovery_available: true,
+    host_supported: true,
     native_bridge_supported: true,
 
     header_libs: [
         "libbase_headers",
-        "libbinder_headers_platform_shared",
+        "libbinder_headers_base",
         "libcutils_headers",
         "libutils_headers",
     ],
     export_header_lib_headers: [
         "libbase_headers",
-        "libbinder_headers_platform_shared",
+        "libbinder_headers_base",
         "libcutils_headers",
         "libutils_headers",
     ],
@@ -87,24 +115,27 @@
         "RpcSession.cpp",
         "RpcServer.cpp",
         "RpcState.cpp",
+        "RpcTransportRaw.cpp",
         "Stability.cpp",
         "Status.cpp",
         "TextOutput.cpp",
-        "Trace.cpp",
         "Utils.cpp",
-    ],
-
-    shared_libs: [
-        "libcutils",
-        "libutils",
-    ],
-
-    static_libs: [
-        "libbase",
+        "file.cpp",
     ],
 
     header_libs: [
-        "libbinder_headers",
+        "libbinder_headers_base",
+    ],
+
+    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",
     ],
 }
 
@@ -120,7 +151,6 @@
     srcs: [
         "OS_android.cpp",
         "OS_unix_base.cpp",
-        "RpcTransportRaw.cpp",
     ],
 
     target: {
@@ -135,15 +165,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: [
@@ -154,11 +175,18 @@
     },
 
     shared_libs: [
+        "libcutils",
         "liblog",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
     ],
 
     header_libs: [
         "jni_headers",
+        "libbinder_headers",
     ],
 
     export_header_lib_headers: [
@@ -215,12 +243,24 @@
     host_supported: true,
 
     header_libs: [
+        "libbinder_headers_base",
+        "liblog_stub",
         "trusty_mock_headers",
     ],
 
+    shared_libs: [
+        "libutils_binder_sdk",
+    ],
+
     cflags: [
         "-DBINDER_RPC_SINGLE_THREADED",
+        "-DBINDER_ENABLE_LIBLOG_ASSERT",
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+        "-DBINDER_DISABLE_BLOB",
+        "-DBINDER_NO_LIBBASE",
+        // TODO: switch to "vendor: true" rather than copying this
         // Trusty libbinder uses vendor stability for its binders
+        "-D__ANDROID_VENDOR__",
         "-D__ANDROID_VNDK__",
         "-U__ANDROID__",
         "-D__TRUSTY__",
@@ -250,8 +290,6 @@
 
     srcs: [
         // Trusty-specific files
-        "OS_android.cpp",
-        "trusty/logging.cpp",
         "trusty/OS.cpp",
         "trusty/RpcServerTrusty.cpp",
         "trusty/RpcTransportTipcTrusty.cpp",
@@ -346,6 +384,44 @@
     afdo: true,
 }
 
+cc_library_host_shared {
+    name: "libbinder_sdk",
+
+    defaults: [
+        "libbinder_common_defaults",
+    ],
+
+    shared_libs: [
+        "libutils_binder_sdk",
+    ],
+
+    cflags: [
+        "-DBINDER_ENABLE_LIBLOG_ASSERT",
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+        "-DBINDER_DISABLE_BLOB",
+        "-DBINDER_NO_LIBBASE",
+    ],
+
+    header_libs: [
+        "liblog_stub",
+    ],
+
+    srcs: [
+        "OS_non_android_linux.cpp",
+        "OS_unix_base.cpp",
+    ],
+
+    visibility: [
+        ":__subpackages__",
+    ],
+
+    target: {
+        windows: {
+            enabled: false,
+        },
+    },
+}
+
 cc_library_static {
     name: "libbinder_rpc_no_kernel",
     vendor_available: true,
@@ -578,11 +654,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.
@@ -651,4 +722,7 @@
         "libutils",
         "android.debug_aidl-cpp",
     ],
+    static_libs: [
+        "libc++fs",
+    ],
 }
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index f22e90a..c57c9cd 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -19,8 +19,6 @@
 #include <atomic>
 #include <set>
 
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
 #include <binder/IPCThreadState.h>
@@ -29,8 +27,8 @@
 #include <binder/Parcel.h>
 #include <binder/RecordedTransaction.h>
 #include <binder/RpcServer.h>
+#include <binder/unique_fd.h>
 #include <pthread.h>
-#include <utils/misc.h>
 
 #include <inttypes.h>
 #include <stdio.h>
@@ -45,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
@@ -169,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;
@@ -271,11 +270,11 @@
     bool mInheritRt = false;
 
     // for below objects
-    Mutex mLock;
+    RpcMutex mLock;
     std::set<sp<RpcServerLink>> mRpcServerLinks;
     BpBinder::ObjectManager mObjects;
 
-    android::base::unique_fd mRecordingFd;
+    unique_fd mRecordingFd;
 };
 
 // ---------------------------------------------------------------------------
@@ -307,9 +306,9 @@
         return PERMISSION_DENIED;
     }
     Extras* e = getOrCreateExtras();
-    AutoMutex lock(e->mLock);
+    RpcMutexUniqueLock lock(e->mLock);
     if (mRecordingOn) {
-        LOG(INFO) << "Could not start Binder recording. Another is already in progress.";
+        ALOGI("Could not start Binder recording. Another is already in progress.");
         return INVALID_OPERATION;
     } else {
         status_t readStatus = data.readUniqueFileDescriptor(&(e->mRecordingFd));
@@ -317,7 +316,7 @@
             return readStatus;
         }
         mRecordingOn = true;
-        LOG(INFO) << "Started Binder recording.";
+        ALOGI("Started Binder recording.");
         return NO_ERROR;
     }
 }
@@ -337,14 +336,14 @@
         return PERMISSION_DENIED;
     }
     Extras* e = getOrCreateExtras();
-    AutoMutex lock(e->mLock);
+    RpcMutexUniqueLock lock(e->mLock);
     if (mRecordingOn) {
         e->mRecordingFd.reset();
         mRecordingOn = false;
-        LOG(INFO) << "Stopped Binder recording.";
+        ALOGI("Stopped Binder recording.");
         return NO_ERROR;
     } else {
-        LOG(INFO) << "Could not stop Binder recording. One is not in progress.";
+        ALOGI("Could not stop Binder recording. One is not in progress.");
         return INVALID_OPERATION;
     }
 }
@@ -378,11 +377,11 @@
             err = stopRecordingTransactions();
             break;
         case EXTENSION_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             err = reply->writeStrongBinder(getExtension());
             break;
         case DEBUG_PID_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             err = reply->writeInt32(getDebugPid());
             break;
         case SET_RPC_CLIENT_TRANSACTION: {
@@ -405,7 +404,7 @@
 
     if (kEnableKernelIpc && mRecordingOn && code != START_RECORDING_TRANSACTION) [[unlikely]] {
         Extras* e = mExtras.load(std::memory_order_acquire);
-        AutoMutex lock(e->mLock);
+        RpcMutexUniqueLock lock(e->mLock);
         if (mRecordingOn) {
             Parcel emptyReply;
             timespec ts;
@@ -415,10 +414,10 @@
                                 reply ? *reply : emptyReply, err);
             if (transaction) {
                 if (status_t err = transaction->dumpToFile(e->mRecordingFd); err != NO_ERROR) {
-                    LOG(INFO) << "Failed to dump RecordedTransaction to file with error " << err;
+                    ALOGI("Failed to dump RecordedTransaction to file with error %d", err);
                 }
             } else {
-                LOG(INFO) << "Failed to create RecordedTransaction object.";
+                ALOGI("Failed to create RecordedTransaction object.");
             }
         }
     }
@@ -452,7 +451,7 @@
     Extras* e = getOrCreateExtras();
     LOG_ALWAYS_FATAL_IF(!e, "no memory");
 
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     return e->mObjects.attach(objectID, object, cleanupCookie, func);
 }
 
@@ -461,7 +460,7 @@
     Extras* e = mExtras.load(std::memory_order_acquire);
     if (!e) return nullptr;
 
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     return e->mObjects.find(objectID);
 }
 
@@ -469,7 +468,7 @@
     Extras* e = mExtras.load(std::memory_order_acquire);
     if (!e) return nullptr;
 
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     return e->mObjects.detach(objectID);
 }
 
@@ -477,7 +476,7 @@
     Extras* e = getOrCreateExtras();
     LOG_ALWAYS_FATAL_IF(!e, "no memory");
 
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     doWithLock();
 }
 
@@ -485,7 +484,7 @@
                                         const void* makeArgs) {
     Extras* e = getOrCreateExtras();
     LOG_ALWAYS_FATAL_IF(!e, "no memory");
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     return e->mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
 }
 
@@ -642,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) {
@@ -654,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;
@@ -692,7 +690,7 @@
     auto weakThis = wp<BBinder>::fromExisting(this);
 
     Extras* e = getOrCreateExtras();
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     auto rpcServer = RpcServer::make();
     LOG_ALWAYS_FATAL_IF(rpcServer == nullptr, "RpcServer::make returns null");
     auto link = sp<RpcServerLink>::make(rpcServer, keepAliveBinder, weakThis);
@@ -706,7 +704,7 @@
         return status;
     }
     rpcServer->setMaxThreads(binderThreadPoolMaxCount);
-    LOG(INFO) << "RpcBinder: Started Binder debug on " << getInterfaceDescriptor();
+    ALOGI("RpcBinder: Started Binder debug on %s", String8(getInterfaceDescriptor()).c_str());
     rpcServer->start();
     e->mRpcServerLinks.emplace(link);
     LOG_RPC_DETAIL("%s(fd=%d) successful", __PRETTY_FUNCTION__, socketFdForPrint);
@@ -716,7 +714,7 @@
 void BBinder::removeRpcServerLink(const sp<RpcServerLink>& link) {
     Extras* e = mExtras.load(std::memory_order_acquire);
     if (!e) return;
-    AutoMutex _l(e->mLock);
+    RpcMutexUniqueLock _l(e->mLock);
     (void)e->mRpcServerLinks.erase(link);
 }
 
@@ -724,20 +722,20 @@
 {
     if (!wasParceled()) {
         if (getExtension()) {
-             ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
+            ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
         }
         if (isRequestingSid()) {
-             ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
+            ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
         }
         if (isInheritRt()) {
-             ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
+            ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
         }
 #ifdef __linux__
         if (getMinSchedulerPolicy() != SCHED_NORMAL) {
-             ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+            ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
         }
         if (getMinSchedulerPriority() != 0) {
-             ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+            ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
         }
 #endif // __linux__
     }
@@ -753,7 +751,7 @@
 {
     switch (code) {
         case INTERFACE_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             reply->writeString16(getInterfaceDescriptor());
             return NO_ERROR;
 
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 3bc4f92..42dd691 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -23,22 +23,22 @@
 #include <binder/IResultReceiver.h>
 #include <binder/RpcSession.h>
 #include <binder/Stability.h>
-#include <utils/Log.h>
 
 #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;
+
 // ---------------------------------------------------------------------------
 
-Mutex BpBinder::sTrackingLock;
+RpcMutex BpBinder::sTrackingLock;
 std::unordered_map<int32_t, uint32_t> BpBinder::sTrackingMap;
 std::unordered_map<int32_t, uint32_t> BpBinder::sLastLimitCallbackMap;
 int BpBinder::sNumTrackedUids = 0;
@@ -163,7 +163,7 @@
     int32_t trackedUid = -1;
     if (sCountByUidEnabled) {
         trackedUid = IPCThreadState::self()->getCallingUid();
-        AutoMutex _l(sTrackingLock);
+        RpcMutexUniqueLock _l(sTrackingLock);
         uint32_t trackedValue = sTrackingMap[trackedUid];
         if (trackedValue & LIMIT_REACHED_MASK) [[unlikely]] {
             if (sBinderProxyThrottleCreate) {
@@ -276,7 +276,7 @@
 }
 
 bool BpBinder::isDescriptorCached() const {
-    Mutex::Autolock _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     return mDescriptorCache.c_str() != kDescriptorUninit.c_str();
 }
 
@@ -292,7 +292,7 @@
         status_t err = thiz->transact(INTERFACE_TRANSACTION, data, &reply);
         if (err == NO_ERROR) {
             String16 res(reply.readString16());
-            Mutex::Autolock _l(mLock);
+            RpcMutexUniqueLock _l(mLock);
             // mDescriptorCache could have been assigned while the lock was
             // released.
             if (mDescriptorCache.c_str() == kDescriptorUninit.c_str()) mDescriptorCache = res;
@@ -319,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);
@@ -385,7 +385,7 @@
             status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
         }
         if (data.dataSize() > LOG_TRANSACTIONS_OVER_SIZE) {
-            Mutex::Autolock _l(mLock);
+            RpcMutexUniqueLock _l(mLock);
             ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d",
                   data.dataSize(), String8(mDescriptorCache).c_str(), code);
         }
@@ -431,7 +431,7 @@
                         "linkToDeath(): recipient must be non-NULL");
 
     {
-        AutoMutex _l(mLock);
+        RpcMutexUniqueLock _l(mLock);
 
         if (!mObitsSent) {
             if (!mObituaries) {
@@ -467,7 +467,7 @@
         return INVALID_OPERATION;
     }
 
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
 
     if (mObitsSent) {
         return DEAD_OBJECT;
@@ -555,30 +555,30 @@
 
 void* BpBinder::attachObject(const void* objectID, void* object, void* cleanupCookie,
                              object_cleanup_func func) {
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     ALOGV("Attaching object %p to binder %p (manager=%p)", object, this, &mObjects);
     return mObjects.attach(objectID, object, cleanupCookie, func);
 }
 
 void* BpBinder::findObject(const void* objectID) const
 {
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     return mObjects.find(objectID);
 }
 
 void* BpBinder::detachObject(const void* objectID) {
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     return mObjects.detach(objectID);
 }
 
 void BpBinder::withLock(const std::function<void()>& doWithLock) {
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     doWithLock();
 }
 
 sp<IBinder> BpBinder::lookupOrCreateWeak(const void* objectID, object_make_func make,
                                          const void* makeArgs) {
-    AutoMutex _l(mLock);
+    RpcMutexUniqueLock _l(mLock);
     return mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
 }
 
@@ -602,7 +602,7 @@
     IPCThreadState* ipc = IPCThreadState::self();
 
     if (mTrackedUid >= 0) {
-        AutoMutex _l(sTrackingLock);
+        RpcMutexUniqueLock _l(sTrackingLock);
         uint32_t trackedValue = sTrackingMap[mTrackedUid];
         if ((trackedValue & COUNTING_VALUE_MASK) == 0) [[unlikely]] {
             ALOGE("Unexpected Binder Proxy tracking decrement in %p handle %d\n", this,
@@ -702,7 +702,7 @@
 
 uint32_t BpBinder::getBinderProxyCount(uint32_t uid)
 {
-    AutoMutex _l(sTrackingLock);
+    RpcMutexUniqueLock _l(sTrackingLock);
     auto it = sTrackingMap.find(uid);
     if (it != sTrackingMap.end()) {
         return it->second & COUNTING_VALUE_MASK;
@@ -717,7 +717,7 @@
 
 void BpBinder::getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts)
 {
-    AutoMutex _l(sTrackingLock);
+    RpcMutexUniqueLock _l(sTrackingLock);
     uids.setCapacity(sTrackingMap.size());
     counts.setCapacity(sTrackingMap.size());
     for (const auto& it : sTrackingMap) {
@@ -731,12 +731,12 @@
 void BpBinder::setCountByUidEnabled(bool enable) { sCountByUidEnabled.store(enable); }
 
 void BpBinder::setLimitCallback(binder_proxy_limit_callback cb) {
-    AutoMutex _l(sTrackingLock);
+    RpcMutexUniqueLock _l(sTrackingLock);
     sLimitCallback = cb;
 }
 
 void BpBinder::setBinderProxyCountWatermarks(int high, int low) {
-    AutoMutex _l(sTrackingLock);
+    RpcMutexUniqueLock _l(sTrackingLock);
     sBinderProxyCountHighWatermark = high;
     sBinderProxyCountLowWatermark = low;
 }
diff --git a/libs/binder/Debug.cpp b/libs/binder/Debug.cpp
index c6e4fb3..7ae616e 100644
--- a/libs/binder/Debug.cpp
+++ b/libs/binder/Debug.cpp
@@ -19,8 +19,6 @@
 
 #include <binder/ProcessState.h>
 
-#include <utils/misc.h>
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index 8ee6cb0..455a433 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -21,16 +21,20 @@
 
 #include <poll.h>
 
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
+#include <binder/Functional.h>
 
+#include "FdUtils.h"
 #include "RpcState.h"
+#include "Utils.h"
+
 namespace android {
 
+using namespace android::binder::impl;
+
 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;
     }
@@ -50,7 +54,7 @@
 #ifdef BINDER_RPC_SINGLE_THREADED
     return mTriggered;
 #else
-    return mWrite == -1;
+    return !mWrite.ok();
 #endif
 }
 
@@ -74,10 +78,9 @@
                         "Only one thread should be polling on Fd!");
 
     transportFd.setPollingState(true);
-    auto pollingStateGuard =
-            android::base::make_scope_guard([&]() { transportFd.setPollingState(false); });
+    auto pollingStateGuard = make_scope_guard([&]() { transportFd.setPollingState(false); });
 
-    int ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
+    int ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
     if (ret < 0) {
         return -errno;
     }
diff --git a/libs/binder/FdTrigger.h b/libs/binder/FdTrigger.h
index 5fbf290..e4a0283 100644
--- a/libs/binder/FdTrigger.h
+++ b/libs/binder/FdTrigger.h
@@ -17,11 +17,10 @@
 
 #include <memory>
 
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
 #include <utils/Errors.h>
 
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 
 namespace android {
 
@@ -62,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/IInterface.cpp b/libs/binder/IInterface.cpp
index 2780bd4..dea2603 100644
--- a/libs/binder/IInterface.cpp
+++ b/libs/binder/IInterface.cpp
@@ -15,7 +15,6 @@
  */
 
 #define LOG_TAG "IInterface"
-#include <utils/Log.h>
 #include <binder/IInterface.h>
 
 namespace android {
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index da58251..b92e504 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -22,7 +22,6 @@
 #include <binder/BpBinder.h>
 #include <binder/TextOutput.h>
 
-#include <android-base/macros.h>
 #include <cutils/sched_policy.h>
 #include <utils/CallStack.h>
 #include <utils/Log.h>
@@ -68,28 +67,28 @@
 
 // Static const and functions will be optimized out if not used,
 // when LOG_NDEBUG and references in IF_LOG_COMMANDS() are optimized out.
-static const char *kReturnStrings[] = {
-    "BR_ERROR",
-    "BR_OK",
-    "BR_TRANSACTION",
-    "BR_REPLY",
-    "BR_ACQUIRE_RESULT",
-    "BR_DEAD_REPLY",
-    "BR_TRANSACTION_COMPLETE",
-    "BR_INCREFS",
-    "BR_ACQUIRE",
-    "BR_RELEASE",
-    "BR_DECREFS",
-    "BR_ATTEMPT_ACQUIRE",
-    "BR_NOOP",
-    "BR_SPAWN_LOOPER",
-    "BR_FINISHED",
-    "BR_DEAD_BINDER",
-    "BR_CLEAR_DEATH_NOTIFICATION_DONE",
-    "BR_FAILED_REPLY",
-    "BR_FROZEN_REPLY",
-    "BR_ONEWAY_SPAM_SUSPECT",
-    "BR_TRANSACTION_SEC_CTX",
+static const char* kReturnStrings[] = {
+        "BR_ERROR",
+        "BR_OK",
+        "BR_TRANSACTION/BR_TRANSACTION_SEC_CTX",
+        "BR_REPLY",
+        "BR_ACQUIRE_RESULT",
+        "BR_DEAD_REPLY",
+        "BR_TRANSACTION_COMPLETE",
+        "BR_INCREFS",
+        "BR_ACQUIRE",
+        "BR_RELEASE",
+        "BR_DECREFS",
+        "BR_ATTEMPT_ACQUIRE",
+        "BR_NOOP",
+        "BR_SPAWN_LOOPER",
+        "BR_FINISHED",
+        "BR_DEAD_BINDER",
+        "BR_CLEAR_DEATH_NOTIFICATION_DONE",
+        "BR_FAILED_REPLY",
+        "BR_FROZEN_REPLY",
+        "BR_ONEWAY_SPAM_SUSPECT",
+        "BR_TRANSACTION_PENDING_FROZEN",
 };
 
 static const char *kCommandStrings[] = {
@@ -395,7 +394,9 @@
 }
 
 void IPCThreadState::checkContextIsBinderForUse(const char* use) const {
-    if (LIKELY(mServingStackPointerGuard == nullptr)) return;
+    if (mServingStackPointerGuard == nullptr) [[likely]] {
+        return;
+    }
 
     if (!mServingStackPointer || mServingStackPointerGuard->address < mServingStackPointer) {
         LOG_ALWAYS_FATAL("In context %s, %s does not make sense (binder sp: %p, guard: %p).",
@@ -832,7 +833,7 @@
     }
 
     if ((flags & TF_ONE_WAY) == 0) {
-        if (UNLIKELY(mCallRestriction != ProcessState::CallRestriction::NONE)) {
+        if (mCallRestriction != ProcessState::CallRestriction::NONE) [[unlikely]] {
             if (mCallRestriction == ProcessState::CallRestriction::ERROR_IF_NOT_ONEWAY) {
                 ALOGE("Process making non-oneway call (code: %u) but is restricted.", code);
                 CallStack::logStack("non-oneway call", CallStack::getCurrent(10).get(),
@@ -842,13 +843,13 @@
             }
         }
 
-        #if 0
+#if 0
         if (code == 4) { // relayout
             ALOGI(">>>>>> CALLING transaction 4");
         } else {
             ALOGI(">>>>>> CALLING transaction %d", code);
         }
-        #endif
+#endif
         if (reply) {
             err = waitForResponse(reply);
         } else {
diff --git a/libs/binder/IResultReceiver.cpp b/libs/binder/IResultReceiver.cpp
index cd92217..60ece72 100644
--- a/libs/binder/IResultReceiver.cpp
+++ b/libs/binder/IResultReceiver.cpp
@@ -18,7 +18,6 @@
 
 #include <binder/IResultReceiver.h>
 
-#include <utils/Log.h>
 #include <binder/Parcel.h>
 #include <utils/String8.h>
 
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 6034f2b..fe566fc 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -200,7 +200,7 @@
 }
 
 bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logPermissionFailure) {
-    static Mutex gPermissionControllerLock;
+    static std::mutex gPermissionControllerLock;
     static sp<IPermissionController> gPermissionController;
 
     sp<IPermissionController> pc;
diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp
index 5b1cb7e..95bdbb4 100644
--- a/libs/binder/MemoryDealer.cpp
+++ b/libs/binder/MemoryDealer.cpp
@@ -155,7 +155,7 @@
     void     dump_l(String8& res, const char* what) const;
 
     static const int    kMemoryAlign;
-    mutable Mutex       mLock;
+    mutable std::mutex mLock;
     LinkedList<chunk_t> mList;
     size_t              mHeapSize;
 };
@@ -305,14 +305,14 @@
 
 size_t SimpleBestFitAllocator::allocate(size_t size, uint32_t flags)
 {
-    Mutex::Autolock _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
     ssize_t offset = alloc(size, flags);
     return offset;
 }
 
 status_t SimpleBestFitAllocator::deallocate(size_t offset)
 {
-    Mutex::Autolock _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
     chunk_t const * const freed = dealloc(offset);
     if (freed) {
         return NO_ERROR;
@@ -420,7 +420,7 @@
 
 void SimpleBestFitAllocator::dump(const char* what) const
 {
-    Mutex::Autolock _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
     dump_l(what);
 }
 
@@ -434,7 +434,7 @@
 void SimpleBestFitAllocator::dump(String8& result,
         const char* what) const
 {
-    Mutex::Autolock _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
     dump_l(result, what);
 }
 
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 8dc1f6a..0035aeb 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -18,14 +18,16 @@
 #include <stddef.h>
 #include <cstdint>
 
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
 #include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
 #include <utils/Errors.h>
 
 namespace android::binder::os {
 
-android::base::Result<void> 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);
 
@@ -33,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_non_android_linux.cpp b/libs/binder/OS_non_android_linux.cpp
new file mode 100644
index 0000000..b525d1a
--- /dev/null
+++ b/libs/binder/OS_non_android_linux.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OS.h"
+
+#include <log/log.h>
+
+#include <syscall.h>
+#include <cstdarg>
+
+#ifdef __ANDROID__
+#error "This module is not intended for Android, just bare Linux"
+#endif
+#ifdef __APPLE__
+#error "This module is not intended for MacOS"
+#endif
+#ifdef _WIN32
+#error "This module is not intended for Windows"
+#endif
+
+namespace android::binder::os {
+
+void trace_begin(uint64_t, const char*) {}
+
+void trace_end(uint64_t) {}
+
+uint64_t GetThreadId() {
+    return syscall(__NR_gettid);
+}
+
+bool report_sysprop_change() {
+    return false;
+}
+
+} // namespace android::binder::os
+
+int __android_log_print(int /*prio*/, const char* /*tag*/, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+
+    return 1;
+}
diff --git a/libs/binder/OS_unix_base.cpp b/libs/binder/OS_unix_base.cpp
index 81933d5..ca998d4 100644
--- a/libs/binder/OS_unix_base.cpp
+++ b/libs/binder/OS_unix_base.cpp
@@ -15,39 +15,41 @@
  */
 
 #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::base::ErrnoError;
-using android::base::Result;
+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;
 
-Result<void> 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) {
-        return ErrnoError() << "Could not get flags for fd";
+        PLOGE("Failed setNonBlocking: Could not get flags for fd");
+        return -errno;
     }
     if (int ret = TEMP_FAILURE_RETRY(fcntl(fd.get(), F_SETFL, flags | O_NONBLOCK)); ret == -1) {
-        return ErrnoError() << "Could not set non-blocking flag for fd";
+        PLOGE("Failed setNonBlocking: Could not set non-blocking flag for fd");
+        return -errno;
     }
-    return {};
+    return OK;
 }
 
 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 a3ff7d2..c1770b3 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "Parcel"
 //#define LOG_NDEBUG 0
 
+#include <endian.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -32,6 +33,7 @@
 
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
+#include <binder/Functional.h>
 #include <binder/IPCThreadState.h>
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
@@ -39,15 +41,11 @@
 #include <binder/Status.h>
 #include <binder/TextOutput.h>
 
-#include <android-base/scopeguard.h>
 #ifndef BINDER_DISABLE_BLOB
 #include <cutils/ashmem.h>
 #endif
-#include <utils/Flattenable.h>
-#include <utils/Log.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
-#include <utils/misc.h>
 
 #include "OS.h"
 #include "RpcState.h"
@@ -98,6 +96,10 @@
 
 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__
 static_assert(sizeof(Parcel) == 120);
@@ -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);
 }
 
@@ -627,7 +629,7 @@
         }
 
         const size_t savedDataPos = mDataPos;
-        base::ScopeGuard scopeGuard = [&]() { mDataPos = savedDataPos; };
+        auto scopeGuard = make_scope_guard([&]() { mDataPos = savedDataPos; });
 
         rpcFields->mObjectPositions.reserve(otherRpcFields->mObjectPositions.size());
         if (otherRpcFields->mFds != nullptr) {
@@ -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 8ec4af9..7de94e3 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -18,10 +18,9 @@
 
 #include <binder/ProcessState.h>
 
-#include <android-base/result.h>
-#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <binder/BpBinder.h>
+#include <binder/Functional.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/Stability.h>
@@ -32,6 +31,7 @@
 #include <utils/Thread.h>
 
 #include "Static.h"
+#include "Utils.h"
 #include "binder_module.h"
 
 #include <errno.h>
@@ -60,6 +60,9 @@
 
 namespace android {
 
+using namespace android::binder::impl;
+using android::binder::unique_fd;
+
 class PoolThread : public Thread
 {
 public:
@@ -189,7 +192,7 @@
 
 void ProcessState::startThreadPool()
 {
-    AutoMutex _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
     if (!mThreadPoolStarted) {
         if (mMaxThreads == 0) {
             // see also getThreadPoolMaxTotalThreadCount
@@ -203,7 +206,7 @@
 
 bool ProcessState::becomeContextManager()
 {
-    AutoMutex _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
 
     flat_binder_object obj {
         .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
@@ -310,7 +313,7 @@
 {
     sp<IBinder> result;
 
-    AutoMutex _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
 
     if (handle == 0 && the_context_object != nullptr) return the_context_object;
 
@@ -374,7 +377,7 @@
 
 void ProcessState::expungeHandle(int32_t handle, IBinder* binder)
 {
-    AutoMutex _l(mLock);
+    std::unique_lock<std::mutex> _l(mLock);
 
     handle_entry* e = lookupHandleLocked(handle);
 
@@ -430,7 +433,7 @@
 
 size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
     pthread_mutex_lock(&mThreadCountLock);
-    base::ScopeGuard detachGuard = [&]() { pthread_mutex_unlock(&mThreadCountLock); };
+    auto detachGuard = make_scope_guard([&]() { pthread_mutex_unlock(&mThreadCountLock); });
 
     if (mThreadPoolStarted) {
         LOG_ALWAYS_FATAL_IF(mKernelStartedThreads > mMaxThreads + 1,
@@ -512,31 +515,31 @@
     return mDriverName;
 }
 
-static base::Result<int> open_driver(const char* driver) {
-    int fd = open(driver, O_RDWR | O_CLOEXEC);
-    if (fd < 0) {
-        return base::ErrnoError() << "Opening '" << driver << "' failed";
+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 {};
     }
     int vers = 0;
-    status_t result = ioctl(fd, BINDER_VERSION, &vers);
+    int result = ioctl(fd.get(), BINDER_VERSION, &vers);
     if (result == -1) {
-        close(fd);
-        return base::ErrnoError() << "Binder ioctl to obtain version failed";
+        PLOGE("Binder ioctl to obtain version failed");
+        return {};
     }
     if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
-        close(fd);
-        return base::Error() << "Binder driver protocol(" << vers
-                             << ") does not match user space protocol("
-                             << BINDER_CURRENT_PROTOCOL_VERSION
-                             << ")! ioctl() return value: " << result;
+        ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)! "
+              "ioctl() return value: %d",
+              vers, BINDER_CURRENT_PROTOCOL_VERSION, result);
+        return {};
     }
     size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
-    result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
+    result = ioctl(fd.get(), BINDER_SET_MAX_THREADS, &maxThreads);
     if (result == -1) {
         ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
     }
     uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
-    result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
+    result = ioctl(fd.get(), BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
     if (result == -1) {
         ALOGE_IF(ProcessState::isDriverFeatureEnabled(
                      ProcessState::DriverFeature::ONEWAY_SPAM_DETECTION),
@@ -561,28 +564,27 @@
         mThreadPoolStarted(false),
         mThreadPoolSeq(1),
         mCallRestriction(CallRestriction::NONE) {
-    base::Result<int> 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.
         mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
-                        opened.value(), 0);
+                        opened.get(), 0);
         if (mVMStart == MAP_FAILED) {
-            close(opened.value());
             // *sigh*
-            opened = base::Error()
-                    << "Using " << driver << " failed: unable to mmap transaction memory.";
+            ALOGE("Using %s failed: unable to mmap transaction memory.", driver);
+            opened.reset();
             mDriverName.clear();
         }
     }
 
 #ifdef __ANDROID__
-    LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating: %s",
-                        driver, opened.error().message().c_str());
+    LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating.",
+                        driver);
 #endif
 
     if (opened.ok()) {
-        mDriverFD = opened.value();
+        mDriverFD = opened.release();
     }
 }
 
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index 3246706..525ba2e 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -14,17 +14,23 @@
  * limitations under the License.
  */
 
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/scopeguard.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)
@@ -126,18 +132,17 @@
 
     t.mData.mInterfaceName = std::string(String8(interfaceName).c_str());
     if (interfaceName.size() != t.mData.mInterfaceName.size()) {
-        LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
-                      "utf-8.";
+        ALOGE("Interface Name is not valid. Contains characters that aren't single byte utf-8.");
         return std::nullopt;
     }
 
     if (t.mSent.setData(dataParcel.data(), dataParcel.dataBufferSize()) != android::NO_ERROR) {
-        LOG(ERROR) << "Failed to set sent parcel data.";
+        ALOGE("Failed to set sent parcel data.");
         return std::nullopt;
     }
 
     if (t.mReply.setData(replyParcel.data(), replyParcel.dataBufferSize()) != android::NO_ERROR) {
-        LOG(ERROR) << "Failed to set reply parcel data.";
+        ALOGE("Failed to set reply parcel data.");
         return std::nullopt;
     }
 
@@ -167,38 +172,37 @@
     const long pageSize = sysconf(_SC_PAGE_SIZE);
     struct stat fileStat;
     if (fstat(fd.get(), &fileStat) != 0) {
-        LOG(ERROR) << "Unable to get file information";
+        ALOGE("Unable to get file information");
         return std::nullopt;
     }
 
     off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
     if (fdCurrentPosition == -1) {
-        LOG(ERROR) << "Invalid offset in file descriptor.";
+        ALOGE("Invalid offset in file descriptor.");
         return std::nullopt;
     }
     do {
         if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) {
-            LOG(ERROR) << "Not enough file remains to contain expected chunk descriptor";
+            ALOGE("Not enough file remains to contain expected chunk descriptor");
             return std::nullopt;
         }
 
-        if (!android::base::ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
-            LOG(ERROR) << "Failed to read ChunkDescriptor from fd " << fd.get() << ". "
-                       << strerror(errno);
+        if (!ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
+            ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno));
             return std::nullopt;
         }
         transaction_checksum_t checksum = *reinterpret_cast<transaction_checksum_t*>(&chunk);
 
         fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
         if (fdCurrentPosition == -1) {
-            LOG(ERROR) << "Invalid offset in file descriptor.";
+            ALOGE("Invalid offset in file descriptor.");
             return std::nullopt;
         }
         off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
         off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
 
         if (chunk.dataSize > kMaxChunkDataSize) {
-            LOG(ERROR) << "Chunk data exceeds maximum size.";
+            ALOGE("Chunk data exceeds maximum size.");
             return std::nullopt;
         }
 
@@ -206,19 +210,19 @@
                 chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
 
         if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) {
-            LOG(ERROR) << "Chunk payload exceeds remaining file size.";
+            ALOGE("Chunk payload exceeds remaining file size.");
             return std::nullopt;
         }
 
         if (PADDING8(chunkPayloadSize) != 0) {
-            LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+            ALOGE("Invalid chunk size, not aligned %zu", chunkPayloadSize);
             return std::nullopt;
         }
 
         size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset;
         void* mappedMemory =
                 mmap(NULL, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart);
-        auto mmap_guard = android::base::make_scope_guard(
+        auto mmap_guard = make_scope_guard(
                 [mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); });
 
         transaction_checksum_t* payloadMap =
@@ -227,8 +231,7 @@
                 sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
                                                 // page-alignment
         if (payloadMap == MAP_FAILED) {
-            LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
-                       << strerror(errno);
+            ALOGE("Memory mapping failed for fd %d: %d %s", fd.get(), errno, strerror(errno));
             return std::nullopt;
         }
         for (size_t checksumIndex = 0;
@@ -236,21 +239,21 @@
             checksum ^= payloadMap[checksumIndex];
         }
         if (checksum != 0) {
-            LOG(ERROR) << "Checksum failed.";
+            ALOGE("Checksum failed.");
             return std::nullopt;
         }
 
         fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
         if (fdCurrentPosition == -1) {
-            LOG(ERROR) << "Invalid offset in file descriptor.";
+            ALOGE("Invalid offset in file descriptor.");
             return std::nullopt;
         }
 
         switch (chunk.chunkType) {
             case HEADER_CHUNK: {
                 if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
-                    LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
-                               << sizeof(TransactionHeader) << ".";
+                    ALOGE("Header Chunk indicated size %" PRIu32 "; Expected %zu.", chunk.dataSize,
+                          sizeof(TransactionHeader));
                     return std::nullopt;
                 }
                 t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
@@ -264,7 +267,7 @@
             case DATA_PARCEL_CHUNK: {
                 if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
                                     chunk.dataSize) != android::NO_ERROR) {
-                    LOG(ERROR) << "Failed to set sent parcel data.";
+                    ALOGE("Failed to set sent parcel data.");
                     return std::nullopt;
                 }
                 break;
@@ -272,7 +275,7 @@
             case REPLY_PARCEL_CHUNK: {
                 if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
                                      chunk.dataSize) != android::NO_ERROR) {
-                    LOG(ERROR) << "Failed to set reply parcel data.";
+                    ALOGE("Failed to set reply parcel data.");
                     return std::nullopt;
                 }
                 break;
@@ -280,7 +283,7 @@
             case END_CHUNK:
                 break;
             default:
-                LOG(INFO) << "Unrecognized chunk.";
+                ALOGI("Unrecognized chunk.");
                 break;
         }
     } while (chunk.chunkType != END_CHUNK);
@@ -291,7 +294,7 @@
 android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
                                                   size_t byteCount, const uint8_t* data) const {
     if (byteCount > kMaxChunkDataSize) {
-        LOG(ERROR) << "Chunk data exceeds maximum size";
+        ALOGE("Chunk data exceeds maximum size");
         return BAD_VALUE;
     }
     ChunkDescriptor descriptor = {.chunkType = chunkType,
@@ -319,8 +322,8 @@
     buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
 
     // Write buffer to file
-    if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
-        LOG(ERROR) << "Failed to write chunk fd " << fd.get();
+    if (!WriteFully(fd, buffer.data(), buffer.size())) {
+        ALOGE("Failed to write chunk fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -330,26 +333,26 @@
     if (NO_ERROR !=
         writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
                    reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
-        LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
+        ALOGE("Failed to write transactionHeader to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR !=
         writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
                    reinterpret_cast<const uint8_t*>(mData.mInterfaceName.c_str()))) {
-        LOG(INFO) << "Failed to write Interface Name Chunk to fd " << fd.get();
+        ALOGI("Failed to write Interface Name Chunk to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
 
     if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataBufferSize(), mSent.data())) {
-        LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
+        ALOGE("Failed to write sent Parcel to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataBufferSize(), mReply.data())) {
-        LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
+        ALOGE("Failed to write reply Parcel to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
-        LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
+        ALOGE("Failed to write end chunk to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 07ab093..d9e926a 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -25,12 +25,11 @@
 #include <thread>
 #include <vector>
 
-#include <android-base/scopeguard.h>
+#include <binder/Functional.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcTransportRaw.h>
 #include <log/log.h>
-#include <utils/Compat.h>
 
 #include "BuildFlags.h"
 #include "FdTrigger.h"
@@ -45,8 +44,9 @@
 
 constexpr size_t kSessionIdBytes = 32;
 
-using base::ScopeGuard;
-using base::unique_fd;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 RpcServer::RpcServer(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {}
 RpcServer::~RpcServer() {
@@ -167,9 +167,9 @@
     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 != -1, "Already started");
+    LOG_ALWAYS_FATAL_IF(mServer.fd.ok(), "Already started");
     mServerSocketModifier = std::move(modifier);
 }
 
@@ -201,7 +201,7 @@
 status_t RpcServer::acceptSocketConnection(const RpcServer& server, RpcTransportFd* out) {
     RpcTransportFd clientSocket(unique_fd(TEMP_FAILURE_RETRY(
             accept4(server.mServer.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK))));
-    if (clientSocket.fd < 0) {
+    if (!clientSocket.fd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not accept4 socket: %s", strerror(savedErrno));
         return -savedErrno;
@@ -214,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) {
@@ -231,10 +231,7 @@
     }
 
     unique_fd fd(std::move(std::get<unique_fd>(fds.back())));
-    if (auto res = binder::os::setNonBlocking(fd); !res.ok()) {
-        ALOGE("Failed setNonBlocking: %s", res.error().message().c_str());
-        return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
-    }
+    if (status_t res = binder::os::setNonBlocking(fd); res != OK) return res;
 
     *out = RpcTransportFd(std::move(fd));
     return OK;
@@ -458,11 +455,12 @@
         LOG_ALWAYS_FATAL_IF(threadId == server->mConnectingThreads.end(),
                             "Must establish connection on owned thread");
         thisThread = std::move(threadId->second);
-        ScopeGuard detachGuard = [&]() {
+        auto detachGuardLambda = [&]() {
             thisThread.detach();
             _l.unlock();
             server->mShutdownCv.notify_all();
         };
+        auto detachGuard = make_scope_guard(std::ref(detachGuardLambda));
         server->mConnectingThreads.erase(threadId);
 
         if (status != OK || server->mShutdownTrigger->isTriggered()) {
@@ -548,7 +546,7 @@
             return;
         }
 
-        detachGuard.Disable();
+        detachGuard.release();
         session->preJoinThreadOwnership(std::move(thisThread));
     }
 
@@ -647,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.");
@@ -659,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 fa8f2b5..16a7f9f 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -26,14 +26,12 @@
 
 #include <string_view>
 
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
 #include <binder/BpBinder.h>
+#include <binder/Functional.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcTransportRaw.h>
 #include <binder/Stability.h>
-#include <utils/Compat.h>
 #include <utils/String8.h>
 
 #include "BuildFlags.h"
@@ -52,7 +50,9 @@
 
 namespace android {
 
-using base::unique_fd;
+using namespace android::binder::impl;
+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);
@@ -158,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,
@@ -187,17 +187,13 @@
     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();
             if (!fd.ok()) return BAD_VALUE;
         }
-        if (auto res = binder::os::setNonBlocking(fd); !res.ok()) {
-            ALOGE("setupPreconnectedClient: %s", res.error().message().c_str());
-            return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
-        }
+        if (status_t res = binder::os::setNonBlocking(fd); res != OK) return res;
 
         RpcTransportFd transportFd(std::move(fd));
         status_t status = initAndAddConnection(std::move(transportFd), sessionId, incoming);
@@ -212,7 +208,7 @@
 
     unique_fd serverFd(TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
 
-    if (serverFd == -1) {
+    if (!serverFd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not connect to /dev/null: %s", strerror(savedErrno));
         return -savedErrno;
@@ -412,7 +408,9 @@
     }
 
 private:
-    DISALLOW_COPY_AND_ASSIGN(JavaThreadAttacher);
+    JavaThreadAttacher(const JavaThreadAttacher&) = delete;
+    void operator=(const JavaThreadAttacher&) = delete;
+
     bool mAttached = false;
 
     static JavaVM* getJavaVM() {
@@ -497,7 +495,7 @@
     if (auto status = initShutdownTrigger(); status != OK) return status;
 
     auto oldProtocolVersion = mProtocolVersion;
-    auto cleanup = base::ScopeGuard([&] {
+    auto cleanup = make_scope_guard([&] {
         // if any threads are started, shut them down
         (void)shutdownAndWait(true);
 
@@ -577,7 +575,7 @@
         if (status_t status = connectAndInit(mId, true /*incoming*/); status != OK) return status;
     }
 
-    cleanup.Disable();
+    cleanup.release();
 
     return OK;
 }
@@ -596,7 +594,7 @@
 
         unique_fd serverFd(TEMP_FAILURE_RETRY(
                 socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-        if (serverFd == -1) {
+        if (!serverFd.ok()) {
             int savedErrno = errno;
             ALOGE("Could not create socket at %s: %s", addr.toString().c_str(),
                   strerror(savedErrno));
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 5046253..fe6e1a3 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -18,10 +18,8 @@
 
 #include "RpcState.h"
 
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
-#include <android-base/stringprintf.h>
 #include <binder/BpBinder.h>
+#include <binder/Functional.h>
 #include <binder/IPCThreadState.h>
 #include <binder/RpcServer.h>
 
@@ -30,6 +28,7 @@
 #include "Utils.h"
 
 #include <random>
+#include <sstream>
 
 #include <inttypes.h>
 
@@ -39,7 +38,9 @@
 
 namespace android {
 
-using base::StringPrintf;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 #if RPC_FLAKE_PRONE
 void rpcMaybeWaitToFlake() {
@@ -329,8 +330,10 @@
         desc = "(not promotable)";
     }
 
-    return StringPrintf("node{%p times sent: %zu times recd: %zu type: %s}",
-                        this->binder.unsafe_get(), this->timesSent, this->timesRecd, desc);
+    std::stringstream ss;
+    ss << "node{" << intptr_t(this->binder.unsafe_get()) << " times sent: " << this->timesSent
+       << " times recd: " << this->timesRecd << " type: " << desc << "}";
+    return ss.str();
 }
 
 RpcState::CommandData::CommandData(size_t size) : mSize(size) {
@@ -354,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<android::base::function_ref<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(),
@@ -379,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,
@@ -412,10 +413,8 @@
             return false;
         }
 #else
-        // TODO(b/305983144)
-        // don't restrict on other platforms, though experimental should
-        // only really be used for testing, we don't have a good way to see
-        // what is shipping outside of Android
+        ALOGE("Cannot use experimental RPC binder protocol outside of Android.");
+        return false;
 #endif
     } else if (version >= RPC_WIRE_PROTOCOL_VERSION_NEXT) {
         ALOGE("Cannot use RPC binder protocol version %u which is unknown (current protocol "
@@ -602,25 +601,24 @@
             {const_cast<uint8_t*>(data.data()), data.dataSize()},
             objectTableSpan.toIovec(),
     };
-    if (status_t status = rpcSend(
-                connection, session, "transaction", iovs, arraysize(iovs),
-                [&] {
-                    if (waitUs > kWaitLogUs) {
-                        ALOGE("Cannot send command, trying to process pending refcounts. Waiting "
-                              "%zuus. Too many oneway calls?",
-                              waitUs);
-                    }
+    auto altPoll = [&] {
+        if (waitUs > kWaitLogUs) {
+            ALOGE("Cannot send command, trying to process pending refcounts. Waiting "
+                  "%zuus. Too many oneway calls?",
+                  waitUs);
+        }
 
-                    if (waitUs > 0) {
-                        usleep(waitUs);
-                        waitUs = std::min(kWaitMaxUs, waitUs * 2);
-                    } else {
-                        waitUs = 1;
-                    }
+        if (waitUs > 0) {
+            usleep(waitUs);
+            waitUs = std::min(kWaitMaxUs, waitUs * 2);
+        } else {
+            waitUs = 1;
+        }
 
-                    return drainCommands(connection, session, CommandType::CONTROL_ONLY);
-                },
-                rpcFields->mFds.get());
+        return drainCommands(connection, session, CommandType::CONTROL_ONLY);
+    };
+    if (status_t status = rpcSend(connection, session, "transaction", iovs, countof(iovs),
+                                  std::ref(altPoll), rpcFields->mFds.get());
         status != OK) {
         // rpcSend calls shutdownAndWait, so all refcounts should be reset. If we ever tolerate
         // errors here, then we may need to undo the binder-sent counts for the transaction as
@@ -651,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)};
@@ -692,7 +690,7 @@
             {&rpcReply, rpcReplyWireSize},
             {data.data(), data.size()},
     };
-    if (status_t status = rpcRec(connection, session, "reply body", iovs, arraysize(iovs), nullptr);
+    if (status_t status = rpcRec(connection, session, "reply body", iovs, countof(iovs), nullptr);
         status != OK)
         return status;
 
@@ -762,14 +760,14 @@
             .bodySize = sizeof(RpcDecStrong),
     };
     iovec iovs[]{{&cmd, sizeof(cmd)}, {&body, sizeof(body)}};
-    return rpcSend(connection, session, "dec ref", iovs, arraysize(iovs), std::nullopt);
+    return rpcSend(connection, session, "dec ref", iovs, countof(iovs), std::nullopt);
 }
 
 status_t RpcState::getAndExecuteCommand(const sp<RpcSession::RpcConnection>& connection,
                                         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 =
@@ -798,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{
@@ -811,11 +809,11 @@
         origGuard = kernelBinderState->pushGetCallingSpGuard(&spGuard);
     }
 
-    base::ScopeGuard guardUnguard = [&]() {
+    auto guardUnguard = make_scope_guard([&]() {
         if (kernelBinderState != nullptr) {
             kernelBinderState->restoreGetCallingSpGuard(origGuard);
         }
-    };
+    });
 #endif // BINDER_WITH_KERNEL_IPC
 
     switch (command.command) {
@@ -838,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);
@@ -865,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.
@@ -1145,7 +1143,7 @@
             {const_cast<uint8_t*>(reply.data()), reply.dataSize()},
             objectTableSpan.toIovec(),
     };
-    return rpcSend(connection, session, "reply", iovs, arraysize(iovs), std::nullopt,
+    return rpcSend(connection, session, "reply", iovs, countof(iovs), std::nullopt,
                    rpcFields->mFds.get());
 }
 
@@ -1220,10 +1218,11 @@
     uint32_t protocolVersion = session->getProtocolVersion().value();
     if (protocolVersion < RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE &&
         !rpcFields->mObjectPositions.empty()) {
-        *errorMsg = StringPrintf("Parcel has attached objects but the session's protocol version "
-                                 "(%" PRIu32 ") is too old, must be at least %" PRIu32,
-                                 protocolVersion,
-                                 RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE);
+        std::stringstream ss;
+        ss << "Parcel has attached objects but the session's protocol version (" << protocolVersion
+           << ") is too old, must be at least "
+           << RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE;
+        *errorMsg = ss.str();
         return BAD_VALUE;
     }
 
@@ -1236,9 +1235,10 @@
             case RpcSession::FileDescriptorTransportMode::UNIX: {
                 constexpr size_t kMaxFdsPerMsg = 253;
                 if (rpcFields->mFds->size() > kMaxFdsPerMsg) {
-                    *errorMsg = StringPrintf("Too many file descriptors in Parcel for unix "
-                                             "domain socket: %zu (max is %zu)",
-                                             rpcFields->mFds->size(), kMaxFdsPerMsg);
+                    std::stringstream ss;
+                    ss << "Too many file descriptors in Parcel for unix domain socket: "
+                       << rpcFields->mFds->size() << " (max is " << kMaxFdsPerMsg << ")";
+                    *errorMsg = ss.str();
                     return BAD_VALUE;
                 }
                 break;
@@ -1249,9 +1249,10 @@
                 // available on Android
                 constexpr size_t kMaxFdsPerMsg = 8;
                 if (rpcFields->mFds->size() > kMaxFdsPerMsg) {
-                    *errorMsg = StringPrintf("Too many file descriptors in Parcel for Trusty "
-                                             "IPC connection: %zu (max is %zu)",
-                                             rpcFields->mFds->size(), kMaxFdsPerMsg);
+                    std::stringstream ss;
+                    ss << "Too many file descriptors in Parcel for Trusty IPC connection: "
+                       << rpcFields->mFds->size() << " (max is " << kMaxFdsPerMsg << ")";
+                    *errorMsg = ss.str();
                     return BAD_VALUE;
                 }
                 break;
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index 1fe71a5..8b84602 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -15,11 +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>
@@ -190,28 +191,29 @@
     [[nodiscard]] status_t rpcSend(
             const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
             const char* what, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds =
+            const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+            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);
@@ -253,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 c089811..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>
 
@@ -29,6 +30,10 @@
 
 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 {
 public:
@@ -54,9 +59,8 @@
 
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            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,
@@ -70,8 +74,8 @@
 
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            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 0c81d83..3819fb6 100644
--- a/libs/binder/RpcTransportTipcAndroid.cpp
+++ b/libs/binder/RpcTransportTipcAndroid.cpp
@@ -26,8 +26,9 @@
 #include "RpcState.h"
 #include "RpcTransportUtils.h"
 
-using android::base::Error;
-using android::base::Result;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 namespace android {
 
@@ -75,9 +76,8 @@
 
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            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,9 +93,8 @@
 
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /*ancillaryFds*/)
-            override {
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            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 efb09e9..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>
@@ -29,6 +30,8 @@
 #include "RpcState.h"
 #include "Utils.h"
 
+#include <sstream>
+
 #define SHOULD_LOG_TLS_DETAIL false
 
 #if SHOULD_LOG_TLS_DETAIL
@@ -38,6 +41,11 @@
 #endif
 
 namespace android {
+
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
+
 namespace {
 
 // Implement BIO for socket that ignores SIGPIPE.
@@ -51,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) {
@@ -61,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) {
@@ -71,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");
@@ -181,10 +189,9 @@
     // |sslError| should be from Ssl::getError().
     // If |sslError| is WANT_READ / WANT_WRITE, poll for POLLIN / POLLOUT respectively. Otherwise
     // return error. Also return error if |fdTrigger| is triggered before or during poll().
-    status_t pollForSslError(
-            const android::RpcTransportFd& fd, int sslError, FdTrigger* fdTrigger,
-            const char* fnString, int additionalEvent,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+    status_t pollForSslError(const android::RpcTransportFd& fd, int sslError, FdTrigger* fdTrigger,
+                             const char* fnString, int additionalEvent,
+                             const std::optional<SmallFunction<status_t()>>& altPoll) {
         switch (sslError) {
             case SSL_ERROR_WANT_READ:
                 return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString, altPoll);
@@ -200,7 +207,7 @@
 
     status_t handlePoll(int event, const android::RpcTransportFd& fd, FdTrigger* fdTrigger,
                         const char* fnString,
-                        const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+                        const std::optional<SmallFunction<status_t()>>& altPoll) {
         status_t ret;
         if (altPoll) {
             ret = (*altPoll)();
@@ -284,13 +291,12 @@
     status_t pollRead(void) override;
     status_t interruptableWriteFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override;
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
     status_t interruptableReadFully(
             FdTrigger* fdTrigger, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override;
+            const std::optional<SmallFunction<status_t()>>& altPoll,
+            std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
 
     bool isWaiting() override { return mSocket.isInPollingState(); };
 
@@ -320,8 +326,8 @@
 
 status_t RpcTransportTls::interruptableWriteFully(
         FdTrigger* fdTrigger, iovec* iovs, int niovs,
-        const std::optional<android::base::function_ref<status_t()>>& altPoll,
-        const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+        const std::optional<SmallFunction<status_t()>>& altPoll,
+        const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
     (void)ancillaryFds;
 
     MAYBE_WAIT_IN_FLAKE_MODE;
@@ -366,8 +372,8 @@
 
 status_t RpcTransportTls::interruptableReadFully(
         FdTrigger* fdTrigger, iovec* iovs, int niovs,
-        const std::optional<android::base::function_ref<status_t()>>& altPoll,
-        std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+        const std::optional<SmallFunction<status_t()>>& altPoll,
+        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 32f0db8..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"
@@ -27,7 +27,7 @@
 status_t interruptableReadOrWrite(
         const android::RpcTransportFd& socket, FdTrigger* fdTrigger, iovec* iovs, int niovs,
         SendOrReceive sendOrReceiveFun, const char* funName, int16_t event,
-        const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+        const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll) {
     MAYBE_WAIT_IN_FLAKE_MODE;
 
     if (niovs < 0) {
diff --git a/libs/binder/RpcTrusty.cpp b/libs/binder/RpcTrusty.cpp
index 3b53b05..a445196 100644
--- a/libs/binder/RpcTrusty.cpp
+++ b/libs/binder/RpcTrusty.cpp
@@ -16,15 +16,14 @@
 
 #define LOG_TAG "RpcTrusty"
 
-#include <android-base/logging.h>
-#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,
@@ -35,13 +34,13 @@
     auto request = [=] {
         int tipcFd = tipc_connect(device, port);
         if (tipcFd < 0) {
-            LOG(ERROR) << "Failed to connect to Trusty service. Error code: " << tipcFd;
+            ALOGE("Failed to connect to Trusty service. Error code: %d", tipcFd);
             return unique_fd();
         }
         return unique_fd(tipcFd);
     };
     if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
-        LOG(ERROR) << "Failed to set up Trusty client. Error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Trusty client. Error: %s", statusToString(status).c_str());
         return nullptr;
     }
     return session;
diff --git a/libs/binder/ServiceManagerHost.cpp b/libs/binder/ServiceManagerHost.cpp
index 2b67f03..9482e3e 100644
--- a/libs/binder/ServiceManagerHost.cpp
+++ b/libs/binder/ServiceManagerHost.cpp
@@ -56,16 +56,16 @@
     [[nodiscard]] const std::optional<unsigned int>& hostPort() const { return mPort; }
 
 private:
-    DISALLOW_COPY_AND_ASSIGN(AdbForwarder);
+    AdbForwarder(const AdbForwarder&) = delete;
+    void operator=(const AdbForwarder&) = delete;
     explicit AdbForwarder(unsigned int port) : mPort(port) {}
     std::optional<unsigned int> mPort;
 };
 std::optional<AdbForwarder> AdbForwarder::forward(unsigned int devicePort) {
     auto result =
             execute({"adb", "forward", "tcp:0", "tcp:" + std::to_string(devicePort)}, nullptr);
-    if (!result.ok()) {
-        ALOGE("Unable to run `adb forward tcp:0 tcp:%d`: %s", devicePort,
-              result.error().message().c_str());
+    if (!result.has_value()) {
+        ALOGE("Unable to run `adb forward tcp:0 tcp:%d`", devicePort);
         return std::nullopt;
     }
     // Must end with exit code 0 (`has_value() && value() == 0`)
@@ -94,9 +94,8 @@
     if (!mPort.has_value()) return;
 
     auto result = execute({"adb", "forward", "--remove", "tcp:" + std::to_string(*mPort)}, nullptr);
-    if (!result.ok()) {
-        ALOGE("Unable to run `adb forward --remove tcp:%d`: %s", *mPort,
-              result.error().message().c_str());
+    if (!result.has_value()) {
+        ALOGE("Unable to run `adb forward --remove tcp:%d`", *mPort);
         return;
     }
     // Must end with exit code 0 (`has_value() && value() == 0`)
@@ -130,8 +129,7 @@
     serviceDispatcherArgs.insert(serviceDispatcherArgs.begin(), prefix.begin(), prefix.end());
 
     auto result = execute(std::move(serviceDispatcherArgs), &CommandResult::stdoutEndsWithNewLine);
-    if (!result.ok()) {
-        ALOGE("%s", result.error().message().c_str());
+    if (!result.has_value()) {
         return nullptr;
     }
 
diff --git a/libs/binder/ServiceManagerHost.h b/libs/binder/ServiceManagerHost.h
index c5310da..941ba3a 100644
--- a/libs/binder/ServiceManagerHost.h
+++ b/libs/binder/ServiceManagerHost.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <android-base/macros.h>
 #include <android/os/IServiceManager.h>
 
 namespace android {
diff --git a/libs/binder/Stability.cpp b/libs/binder/Stability.cpp
index c432b3a..665dfea 100644
--- a/libs/binder/Stability.cpp
+++ b/libs/binder/Stability.cpp
@@ -73,6 +73,14 @@
     (void)setRepr(binder, getLocalLevel(), REPR_NONE);
 }
 
+// after deprecation of the VNDK, these should be aliases. At some point
+// all references to __ANDROID_VNDK__ should be replaced by __ANDROID_VENDOR__
+// but for right now, check that this condition holds because some
+// places check __ANDROID_VNDK__ and some places check __ANDROID_VENDOR__
+#if defined(__ANDROID_VNDK__) != defined(__ANDROID_VENDOR__)
+#error "__ANDROID_VNDK__ and __ANDROID_VENDOR__ should be aliases"
+#endif
+
 Stability::Level Stability::getLocalLevel() {
 #ifdef __ANDROID_APEX__
 #error "APEX can't use libbinder (must use libbinder_ndk)"
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/Utils.cpp b/libs/binder/Utils.cpp
index 47fd17d..d9a96af 100644
--- a/libs/binder/Utils.cpp
+++ b/libs/binder/Utils.cpp
@@ -16,7 +16,6 @@
 
 #include "Utils.h"
 
-#include <android-base/logging.h>
 #include <string.h>
 
 namespace android {
@@ -26,7 +25,7 @@
 }
 
 std::string HexString(const void* bytes, size_t len) {
-    CHECK(bytes != nullptr || len == 0) << bytes << " " << len;
+    LOG_ALWAYS_FATAL_IF(len > 0 && bytes == nullptr, "%p %zu", bytes, len);
 
     // b/132916539: Doing this the 'C way', std::setfill triggers ubsan implicit conversion
     const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h
index dd632c0..eec09eb 100644
--- a/libs/binder/Utils.h
+++ b/libs/binder/Utils.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#pragma once
+
 #include <stddef.h>
 #include <sys/uio.h>
 #include <cstdint>
@@ -22,6 +24,30 @@
 #include <log/log.h>
 #include <utils/Errors.h>
 
+#define PLOGE(fmt, ...)                                                     \
+    do {                                                                    \
+        auto savedErrno = errno;                                            \
+        ALOGE(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+    } while (0)
+#define PLOGF(fmt, ...)                                                                \
+    do {                                                                               \
+        auto savedErrno = errno;                                                       \
+        LOG_ALWAYS_FATAL(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+    } while (0)
+
+/* TEMP_FAILURE_RETRY is not available on macOS and Trusty. */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp)                \
+    ({                                         \
+        __typeof__(exp) _rc;                   \
+        do {                                   \
+            _rc = (exp);                       \
+        } while (_rc == -1 && errno == EINTR); \
+        _rc;                                   \
+    })
+#endif
+
 #define TEST_AND_RETURN(value, expr)            \
     do {                                        \
         if (!(expr)) {                          \
@@ -32,6 +58,17 @@
 
 namespace android {
 
+/**
+ * Get the size of a statically initialized array.
+ *
+ * \param N the array to get the size of.
+ * \return the size of the array.
+ */
+template <typename T, size_t N>
+constexpr size_t countof(T (&)[N]) {
+    return N;
+}
+
 // avoid optimizations
 void zeroMemory(uint8_t* data, size_t size);
 
diff --git a/libs/binder/UtilsHost.cpp b/libs/binder/UtilsHost.cpp
index 52b8f69..ae1a6c4 100644
--- a/libs/binder/UtilsHost.cpp
+++ b/libs/binder/UtilsHost.cpp
@@ -25,8 +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) {
@@ -72,8 +77,8 @@
     return ss.str();
 }
 
-android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
-                                             const std::function<bool(const CommandResult&)>& end) {
+std::optional<CommandResult> execute(std::vector<std::string> argStringVec,
+                                     const std::function<bool(const CommandResult&)>& end) {
     // turn vector<string> into null-terminated char* vector.
     std::vector<char*> argv;
     argv.reserve(argStringVec.size() + 1);
@@ -81,15 +86,22 @@
     argv.push_back(nullptr);
 
     CommandResult ret;
-    android::base::unique_fd outWrite;
-    if (!android::base::Pipe(&ret.outPipe, &outWrite))
-        return android::base::ErrnoError() << "pipe() for outPipe";
-    android::base::unique_fd errWrite;
-    if (!android::base::Pipe(&ret.errPipe, &errWrite))
-        return android::base::ErrnoError() << "pipe() for errPipe";
+    unique_fd outWrite;
+    if (!binder::Pipe(&ret.outPipe, &outWrite)) {
+        PLOGE("pipe() for outPipe");
+        return {};
+    }
+    unique_fd errWrite;
+    if (!binder::Pipe(&ret.errPipe, &errWrite)) {
+        PLOGE("pipe() for errPipe");
+        return {};
+    }
 
     int pid = fork();
-    if (pid == -1) return android::base::ErrnoError() << "fork()";
+    if (pid == -1) {
+        PLOGE("fork()");
+        return {};
+    }
     if (pid == 0) {
         // child
         ret.outPipe.reset();
@@ -111,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];
@@ -140,12 +152,19 @@
             *errPollFd = {.fd = ret.errPipe.get(), .events = POLLIN};
         }
         int pollRet = poll(fds, nfds, 1000 /* ms timeout */);
-        if (pollRet == -1) return android::base::ErrnoError() << "poll()";
+        if (pollRet == -1) {
+            PLOGE("poll()");
+            return {};
+        }
 
-        if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdoutStr))
-            return android::base::ErrnoError() << "read(stdout)";
-        if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderrStr))
-            return android::base::ErrnoError() << "read(stderr)";
+        if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdoutStr)) {
+            PLOGE("read(stdout)");
+            return {};
+        }
+        if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderrStr)) {
+            PLOGE("read(stderr)");
+            return {};
+        }
 
         if (end && end(ret)) return ret;
     }
@@ -154,7 +173,10 @@
     while (ret.pid.has_value()) {
         int status;
         auto exitPid = waitpid(pid, &status, 0);
-        if (exitPid == -1) return android::base::ErrnoError() << "waitpid(" << pid << ")";
+        if (exitPid == -1) {
+            PLOGE("waitpid(%d)", pid);
+            return {};
+        }
         if (exitPid == pid) {
             if (WIFEXITED(status)) {
                 ret.pid = std::nullopt;
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
index 98ac4e0..b582f17 100644
--- a/libs/binder/UtilsHost.h
+++ b/libs/binder/UtilsHost.h
@@ -23,8 +23,8 @@
 #include <vector>
 
 #include <android-base/macros.h>
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
+#include <utils/Errors.h>
 
 /**
  * Log a lot more information about host-device binder communication, when debugging issues.
@@ -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); }
@@ -67,7 +67,8 @@
     }
 
 private:
-    DISALLOW_COPY_AND_ASSIGN(CommandResult);
+    CommandResult(const CommandResult&) = delete;
+    void operator=(const CommandResult&) = delete;
 };
 
 std::ostream& operator<<(std::ostream& os, const CommandResult& res);
@@ -94,6 +95,6 @@
 //
 // If the parent process has encountered any errors for system calls, return ExecuteError with
 // the proper errno set.
-android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
-                                             const std::function<bool(const CommandResult&)>& end);
+std::optional<CommandResult> execute(std::vector<std::string> argStringVec,
+                                     const std::function<bool(const CommandResult&)>& end);
 } // namespace android
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 28fb9f1..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 <utils/Mutex.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();
 
@@ -193,7 +193,7 @@
             void                reportOneDeath(const Obituary& obit);
             bool                isDescriptorCached() const;
 
-    mutable Mutex               mLock;
+    mutable RpcMutex            mLock;
             volatile int32_t    mAlive;
             volatile int32_t    mObitsSent;
             Vector<Obituary>*   mObituaries;
@@ -201,7 +201,7 @@
     mutable String16            mDescriptorCache;
             int32_t             mTrackedUid;
 
-    static Mutex                                sTrackingLock;
+    static RpcMutex                             sTrackingLock;
     static std::unordered_map<int32_t,uint32_t> sTrackingMap;
     static int                                  sNumTrackedUids;
     static std::atomic_bool                     sCountByUidEnabled;
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/Functional.h b/libs/binder/include/binder/Functional.h
new file mode 100644
index 0000000..08e3b21
--- /dev/null
+++ b/libs/binder/include/binder/Functional.h
@@ -0,0 +1,50 @@
+/*
+ * 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 <functional>
+#include <memory>
+
+namespace android::binder::impl {
+
+template <typename F>
+constexpr void assert_small_callable() {
+    // While this buffer (std::function::__func::__buf_) is an implementation detail generally not
+    // accessible to users, it's a good bet to assume its size to be around 3 pointers.
+    constexpr size_t kFunctionBufferSize = 3 * sizeof(void*);
+
+    static_assert(sizeof(F) <= kFunctionBufferSize,
+                  "Supplied callable is larger than std::function optimization buffer. "
+                  "Try using std::ref, but make sure lambda lives long enough to be called.");
+}
+
+template <typename F>
+std::unique_ptr<void, std::function<void(void*)>> make_scope_guard(F&& f) {
+    assert_small_callable<decltype(std::bind(f))>();
+    return {reinterpret_cast<void*>(true), std::bind(f)};
+}
+
+template <typename T>
+class SmallFunction : public std::function<T> {
+public:
+    template <typename F>
+    SmallFunction(F&& f) : std::function<T>(f) {
+        assert_small_callable<F>();
+    }
+};
+
+} // namespace android::binder::impl
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/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 9347ce4..dc5b1a1 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -62,8 +62,13 @@
 
             /**
              * Returns the PID of the process which has made the current binder
-             * call. If not in a binder call, this will return getpid. If the
-             * call is oneway, this will return 0.
+             * call. If not in a binder call, this will return getpid.
+             *
+             * Warning: oneway transactions do not receive PID. Even if you expect
+             * a transaction to be synchronous, a misbehaving client could send it
+             * as an asynchronous call and result in a 0 PID here. Additionally, if
+             * there is a race and the calling process dies, the PID may still be
+             * 0 for a synchronous call.
              */
             [[nodiscard]] pid_t getCallingPid() const;
 
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 98d12bb..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
@@ -33,7 +33,6 @@
 #include <utils/RefBase.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
-#include <utils/Flattenable.h>
 
 #include <binder/IInterface.h>
 #include <binder/Parcelable.h>
@@ -355,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
@@ -580,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.
@@ -630,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);
@@ -707,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.
@@ -958,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.
@@ -1107,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.
@@ -1329,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/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 9dc370b..3672702 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -17,13 +17,13 @@
 #pragma once
 
 #include <binder/IBinder.h>
-#include <utils/KeyedVector.h>
-#include <utils/Mutex.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
 
 #include <pthread.h>
 
+#include <mutex>
+
 // ---------------------------------------------------------------------------
 namespace android {
 
@@ -178,7 +178,7 @@
     // Time when thread pool was emptied
     int64_t mStarvationStartTimeMs;
 
-    mutable Mutex mLock; // protects everything below.
+    mutable std::mutex mLock; // protects everything below.
 
     Vector<handle_entry> mHandleToObject;
 
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 cb64603..11fbde9 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -15,11 +15,10 @@
  */
 #pragma once
 
-#include <android-base/threads.h>
-#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>
 
@@ -124,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.
@@ -146,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 6db9ad9..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/function_ref.h>
-#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>
 
@@ -85,13 +85,14 @@
      *   error - interrupted (failure or trigger)
      */
     [[nodiscard]] virtual status_t interruptableWriteFully(
-            FdTrigger *fdTrigger, iovec *iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>> &altPoll,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;
+            FdTrigger* fdTrigger, iovec* iovs, int niovs,
+            const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+            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<android::base::function_ref<status_t()>> &altPoll,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;
+            FdTrigger* fdTrigger, iovec* iovs, int niovs,
+            const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+            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 f51cd9b..cb44c58 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -16,13 +16,18 @@
 
 #include <binder_rpc_unstable.hpp>
 
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
 #include <android/binder_libbinder.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
+#include <binder/unique_fd.h>
+
+#ifndef __TRUSTY__
 #include <cutils/sockets.h>
+#endif
+
+#ifdef __linux__
 #include <linux/vm_sockets.h>
+#endif // __linux__
 
 using android::OK;
 using android::RpcServer;
@@ -30,7 +35,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 {};
@@ -75,6 +80,7 @@
 
 extern "C" {
 
+#ifndef __TRUSTY__
 ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port) {
     auto server = RpcServer::make();
 
@@ -85,8 +91,8 @@
     }
 
     if (status_t status = server->setupVsockServer(bindCid, port); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock server with port " << port
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up vsock server with port %u error: %s", port,
+              statusToString(status).c_str());
         return nullptr;
     }
     if (cid != VMADDR_CID_ANY) {
@@ -95,7 +101,7 @@
             const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
             LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
             if (cid != vaddr->svm_cid) {
-                LOG(ERROR) << "Rejected vsock connection from CID " << vaddr->svm_cid;
+                ALOGE("Rejected vsock connection from CID %u", vaddr->svm_cid);
                 return false;
             }
             return true;
@@ -109,12 +115,12 @@
     auto server = RpcServer::make();
     auto fd = unique_fd(socketFd);
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid socket fd " << socketFd;
+        ALOGE("Invalid socket fd %d", socketFd);
         return nullptr;
     }
     if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
-        LOG(ERROR) << "Failed to set up RPC server with fd " << socketFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up RPC server with fd %d error: %s", socketFd,
+              statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -125,13 +131,13 @@
     auto server = RpcServer::make();
     auto fd = unique_fd(bootstrapFd);
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        ALOGE("Invalid bootstrap fd %d", bootstrapFd);
         return nullptr;
     }
     if (status_t status = server->setupUnixDomainSocketBootstrapServer(std::move(fd));
         status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC server with bootstrap fd " << bootstrapFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC server with bootstrap fd %d error: %s", bootstrapFd,
+              statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -141,13 +147,14 @@
 ARpcServer* ARpcServer_newInet(AIBinder* service, const char* address, unsigned int port) {
     auto server = RpcServer::make();
     if (status_t status = server->setupInetServer(address, port, nullptr); status != OK) {
-        LOG(ERROR) << "Failed to set up inet RPC server with address " << address << " and port "
-                   << port << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up inet RPC server with address %s and port %u error: %s", address,
+              port, statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
     return createObjectHandle<ARpcServer>(server);
 }
+#endif // __TRUSTY__
 
 void ARpcServer_setSupportedFileDescriptorTransportModes(
         ARpcServer* handle, const ARpcSession_FileDescriptorTransportMode modes[],
@@ -188,11 +195,12 @@
     freeObjectHandle<RpcSession>(handle);
 }
 
+#ifndef __TRUSTY__
 AIBinder* ARpcSession_setupVsockClient(ARpcSession* handle, unsigned int cid, unsigned int port) {
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupVsockClient(cid, port); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up vsock client with CID %u and port %u error: %s", cid, port,
+              statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -203,8 +211,8 @@
     pathname = ANDROID_SOCKET_DIR "/" + pathname;
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupUnixDomainClient(pathname.c_str()); status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC client with path: " << pathname
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC client with path: %s error: %s", pathname.c_str(),
+              statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -214,13 +222,13 @@
     auto session = handleToStrongPointer<RpcSession>(handle);
     auto fd = unique_fd(dup(bootstrapFd));
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        ALOGE("Invalid bootstrap fd %d", bootstrapFd);
         return nullptr;
     }
     if (status_t status = session->setupUnixDomainSocketBootstrapClient(std::move(fd));
         status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC client with bootstrap fd: " << bootstrapFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC client with bootstrap fd: %d error: %s",
+              bootstrapFd, statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -229,19 +237,20 @@
 AIBinder* ARpcSession_setupInet(ARpcSession* handle, const char* address, unsigned int port) {
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupInetClient(address, port); status != OK) {
-        LOG(ERROR) << "Failed to set up inet RPC client with address " << address << " and port "
-                   << port << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up inet RPC client with address %s and port %u error: %s", address,
+              port, statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
+#endif // __TRUSTY__
 
 AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* handle, int (*requestFd)(void* param),
                                               void* param) {
     auto session = handleToStrongPointer<RpcSession>(handle);
     auto request = [=] { return unique_fd{requestFd(param)}; };
     if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock client. error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up preconnected client. error: %s", statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
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..91c9632
--- /dev/null
+++ b/libs/binder/liblog_stub/include/log/log.h
@@ -0,0 +1,93 @@
+/*
+ * 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;
+}
+
+#ifdef ANDROID_LOG_STUB_WEAK_PRINT
+#define __ANDROID_LOG_STUB_IS_PRINT_PRESENT __android_log_print
+#define __ANDROID_LOG_STUB_PRINT_ATTR __attribute__((weak))
+#else
+#define __ANDROID_LOG_STUB_IS_PRINT_PRESENT true
+#define __ANDROID_LOG_STUB_PRINT_ATTR
+#endif
+
+int __android_log_print(int prio, const char* tag, const char* fmt, ...)
+        __attribute__((format(printf, 3, 4))) __ANDROID_LOG_STUB_PRINT_ATTR;
+
+#define IF_ALOG(priority, tag) \
+    if (__android_log_stub_is_loggable(ANDROID_##priority) && __ANDROID_LOG_STUB_IS_PRINT_PRESENT)
+#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 58ed418..ccf3ce8 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -60,6 +60,7 @@
         "libbinder.cpp",
         "parcel.cpp",
         "parcel_jni.cpp",
+        "persistable_bundle.cpp",
         "process.cpp",
         "stability.cpp",
         "status.cpp",
@@ -138,6 +139,7 @@
         "performance*",
         "portability*",
     ],
+    afdo: true,
 }
 
 cc_library_headers {
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 47da296..bf7a0ba 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_stability.h>
@@ -48,8 +47,8 @@
 void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
 
 static void attach(const sp<IBinder>& binder) {
-    // can only attach once
-    CHECK_EQ(nullptr, binder->attachObject(kId, kValue, nullptr /*cookie*/, clean));
+    auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
+    LOG_ALWAYS_FATAL_IF(alreadyAttached != nullptr, "can only attach once");
 }
 static bool has(const sp<IBinder>& binder) {
     return binder != nullptr && binder->findObject(kId) == kValue;
@@ -65,9 +64,9 @@
 };
 void clean(const void* id, void* obj, void* cookie) {
     // be weary of leaks!
-    // LOG(INFO) << "Deleting an ABpBinder";
+    // ALOGI("Deleting an ABpBinder");
 
-    CHECK(id == kId) << id << " " << obj << " " << cookie;
+    LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
 
     delete static_cast<Value*>(obj);
 };
@@ -121,14 +120,13 @@
     if (mClazz != nullptr && !asABpBinder()) {
         const String16& currentDescriptor = mClazz->getInterfaceDescriptor();
         if (newDescriptor == currentDescriptor) {
-            LOG(ERROR) << __func__ << ": Class descriptors '" << currentDescriptor
-                       << "' match during associateClass, but they are different class objects ("
-                       << clazz << " vs " << mClazz << "). Class descriptor collision?";
+            ALOGE("Class descriptors '%s' match during associateClass, but they are different class"
+                  " objects (%p vs %p). Class descriptor collision?",
+                  String8(currentDescriptor).c_str(), clazz, mClazz);
         } else {
-            LOG(ERROR) << __func__
-                       << ": Class cannot be associated on object which already has a class. "
-                          "Trying to associate to '"
-                       << newDescriptor << "' but already set to '" << currentDescriptor << "'.";
+            ALOGE("%s: Class cannot be associated on object which already has a class. "
+                  "Trying to associate to '%s' but already set to '%s'.",
+                  __func__, String8(newDescriptor).c_str(), String8(currentDescriptor).c_str());
         }
 
         // always a failure because we know mClazz != clazz
@@ -141,13 +139,12 @@
     // more flake-proof. However, the check is not dependent on the lock.
     if (descriptor != newDescriptor && !(asABpBinder() && asABpBinder()->isServiceFuzzing())) {
         if (getBinder()->isBinderAlive()) {
-            LOG(ERROR) << __func__ << ": Expecting binder to have class '" << newDescriptor
-                       << "' but descriptor is actually '" << SanitizeString(descriptor) << "'.";
+            ALOGE("%s: Expecting binder to have class '%s' but descriptor is actually '%s'.",
+                  __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
         } else {
             // b/155793159
-            LOG(ERROR) << __func__ << ": Cannot associate class '" << newDescriptor
-                       << "' to dead binder with cached descriptor '" << SanitizeString(descriptor)
-                       << "'.";
+            ALOGE("%s: Cannot associate class '%s' to dead binder with cached descriptor '%s'.",
+                  __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
         }
         return false;
     }
@@ -164,7 +161,7 @@
 
 ABBinder::ABBinder(const AIBinder_Class* clazz, void* userData)
     : AIBinder(clazz), BBinder(), mUserData(userData) {
-    CHECK(clazz != nullptr);
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "clazz == nullptr");
 }
 ABBinder::~ABBinder() {
     getClass()->onDestroy(mUserData);
@@ -184,7 +181,7 @@
     // technically UINT32_MAX would be okay here, but INT32_MAX is expected since this may be
     // null in Java
     if (args.size() > INT32_MAX) {
-        LOG(ERROR) << "ABBinder::dump received too many arguments: " << args.size();
+        ALOGE("ABBinder::dump received too many arguments: %zu", args.size());
         return STATUS_BAD_VALUE;
     }
 
@@ -263,7 +260,7 @@
 
 ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
     : AIBinder(nullptr /*clazz*/), mRemote(binder) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 }
 ABpBinder::~ABpBinder() {}
 
@@ -373,27 +370,27 @@
 }
 
 void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) {
-    CHECK(clazz != nullptr) << "setOnDump requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setOnDump requires non-null clazz");
 
     // this is required to be called before instances are instantiated
     clazz->onDump = onDump;
 }
 
 void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) {
-    CHECK(clazz != nullptr) << "disableInterfaceTokenHeader requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "disableInterfaceTokenHeader requires non-null clazz");
 
     clazz->writeHeader = false;
 }
 
 void AIBinder_Class_setHandleShellCommand(AIBinder_Class* clazz,
                                           AIBinder_handleShellCommand handleShellCommand) {
-    CHECK(clazz != nullptr) << "setHandleShellCommand requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setHandleShellCommand requires non-null clazz");
 
     clazz->handleShellCommand = handleShellCommand;
 }
 
 const char* AIBinder_Class_getDescriptor(const AIBinder_Class* clazz) {
-    CHECK(clazz != nullptr) << "getDescriptor requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "getDescriptor requires non-null clazz");
 
     return clazz->getInterfaceDescriptorUtf8();
 }
@@ -405,8 +402,8 @@
 }
 
 void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
-    CHECK(who == mWho) << who.unsafe_get() << "(" << who.get_refs() << ") vs " << mWho.unsafe_get()
-                       << " (" << mWho.get_refs() << ")";
+    LOG_ALWAYS_FATAL_IF(who != mWho, "%p (%p) vs %p (%p)", who.unsafe_get(), who.get_refs(),
+                        mWho.unsafe_get(), mWho.get_refs());
 
     mOnDied(mCookie);
 
@@ -417,7 +414,7 @@
     if (recipient != nullptr && strongWho != nullptr) {
         status_t result = recipient->unlinkToDeath(strongWho, mCookie);
         if (result != ::android::DEAD_OBJECT) {
-            LOG(WARNING) << "Unlinking to dead binder resulted in: " << result;
+            ALOGW("Unlinking to dead binder resulted in: %d", result);
         }
     }
 
@@ -426,7 +423,7 @@
 
 AIBinder_DeathRecipient::AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinderDied onDied)
     : mOnDied(onDied), mOnUnlinked(nullptr) {
-    CHECK(onDied != nullptr);
+    LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
 }
 
 void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
@@ -438,7 +435,7 @@
 }
 
 binder_status_t AIBinder_DeathRecipient::linkToDeath(const sp<IBinder>& binder, void* cookie) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
@@ -459,7 +456,7 @@
 }
 
 binder_status_t AIBinder_DeathRecipient::unlinkToDeath(const sp<IBinder>& binder, void* cookie) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
@@ -471,9 +468,8 @@
 
             status_t status = binder->unlinkToDeath(recipient, cookie, 0 /*flags*/);
             if (status != ::android::OK) {
-                LOG(ERROR) << __func__
-                           << ": removed reference to death recipient but unlink failed: "
-                           << statusToString(status);
+                ALOGE("%s: removed reference to death recipient but unlink failed: %s", __func__,
+                      statusToString(status).c_str());
             }
             return PruneStatusT(status);
         }
@@ -490,7 +486,7 @@
 
 AIBinder* AIBinder_new(const AIBinder_Class* clazz, void* args) {
     if (clazz == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide class to construct local binder.";
+        ALOGE("%s: Must provide class to construct local binder.", __func__);
         return nullptr;
     }
 
@@ -554,8 +550,7 @@
 binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                      void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
-                   << recipient << ")";
+        ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -566,8 +561,7 @@
 binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                        void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
-                   << recipient << ")";
+        ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -596,7 +590,7 @@
 }
 void AIBinder_decStrong(AIBinder* binder) {
     if (binder == nullptr) {
-        LOG(ERROR) << __func__ << ": on null binder";
+        ALOGE("%s: on null binder", __func__);
         return;
     }
 
@@ -604,7 +598,7 @@
 }
 int32_t AIBinder_debugGetRefCount(AIBinder* binder) {
     if (binder == nullptr) {
-        LOG(ERROR) << __func__ << ": on null binder";
+        ALOGE("%s: on null binder", __func__);
         return -1;
     }
 
@@ -642,15 +636,14 @@
 
 binder_status_t AIBinder_prepareTransaction(AIBinder* binder, AParcel** in) {
     if (binder == nullptr || in == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder
-                   << ") and in (" << in << ").";
+        ALOGE("%s: requires non-null parameters binder (%p) and in (%p).", __func__, binder, in);
         return STATUS_UNEXPECTED_NULL;
     }
     const AIBinder_Class* clazz = binder->getClass();
     if (clazz == nullptr) {
-        LOG(ERROR) << __func__
-                   << ": Class must be defined for a remote binder transaction. See "
-                      "AIBinder_associateClass.";
+        ALOGE("%s: Class must be defined for a remote binder transaction. See "
+              "AIBinder_associateClass.",
+              __func__);
         return STATUS_INVALID_OPERATION;
     }
 
@@ -683,7 +676,7 @@
 binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, AParcel** in,
                                   AParcel** out, binder_flags_t flags) {
     if (in == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null in parameter";
+        ALOGE("%s: requires non-null in parameter", __func__);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -693,27 +686,26 @@
     AutoParcelDestroyer forIn(in, DestroyParcel);
 
     if (!isUserCommand(code)) {
-        LOG(ERROR) << __func__
-                   << ": Only user-defined transactions can be made from the NDK, but requested: "
-                   << code;
+        ALOGE("%s: Only user-defined transactions can be made from the NDK, but requested: %d",
+              __func__, code);
         return STATUS_UNKNOWN_TRANSACTION;
     }
 
     constexpr binder_flags_t kAllFlags = FLAG_PRIVATE_VENDOR | FLAG_ONEWAY | FLAG_CLEAR_BUF;
     if ((flags & ~kAllFlags) != 0) {
-        LOG(ERROR) << __func__ << ": Unrecognized flags sent: " << flags;
+        ALOGE("%s: Unrecognized flags sent: %d", __func__, flags);
         return STATUS_BAD_VALUE;
     }
 
     if (binder == nullptr || *in == nullptr || out == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder << "), in ("
-                   << in << "), and out (" << out << ").";
+        ALOGE("%s: requires non-null parameters binder (%p), in (%p), and out (%p).", __func__,
+              binder, in, out);
         return STATUS_UNEXPECTED_NULL;
     }
 
     if ((*in)->getBinder() != binder) {
-        LOG(ERROR) << __func__ << ": parcel is associated with binder object " << binder
-                   << " but called with " << (*in)->getBinder();
+        ALOGE("%s: parcel is associated with binder object %p but called with %p", __func__, binder,
+              (*in)->getBinder());
         return STATUS_BAD_VALUE;
     }
 
@@ -733,7 +725,7 @@
 AIBinder_DeathRecipient* AIBinder_DeathRecipient_new(
         AIBinder_DeathRecipient_onBinderDied onBinderDied) {
     if (onBinderDied == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null onBinderDied parameter.";
+        ALOGE("%s: requires non-null onBinderDied parameter.", __func__);
         return nullptr;
     }
     auto ret = new AIBinder_DeathRecipient(onBinderDied);
@@ -799,9 +791,8 @@
 
 void AIBinder_setRequestingSid(AIBinder* binder, bool requestingSid) {
     ABBinder* localBinder = binder->asABBinder();
-    if (localBinder == nullptr) {
-        LOG(FATAL) << "AIBinder_setRequestingSid must be called on a local binder";
-    }
+    LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+                        "AIBinder_setRequestingSid must be called on a local binder");
 
     localBinder->setRequestingSid(requestingSid);
 }
@@ -816,9 +807,8 @@
 
 void AIBinder_setInheritRt(AIBinder* binder, bool inheritRt) {
     ABBinder* localBinder = binder->asABBinder();
-    if (localBinder == nullptr) {
-        LOG(FATAL) << "AIBinder_setInheritRt must be called on a local binder";
-    }
+    LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+                        "AIBinder_setInheritRt must be called on a local binder");
 
     localBinder->setInheritRt(inheritRt);
 }
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
new file mode 100644
index 0000000..a0e4f7b
--- /dev/null
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -0,0 +1,515 @@
+/*
+ * 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 <android/binder_parcel.h>
+#include <android/persistable_bundle.h>
+#include <sys/cdefs.h>
+
+#include <set>
+#include <sstream>
+
+namespace aidl::android::os {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * Takes ownership of the APersistableBundle* given to it in reset() and will automatically
+ * destroy it in the destructor, similar to a smart pointer container
+ */
+class PersistableBundle {
+   public:
+    PersistableBundle() noexcept {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_new();
+        }
+    }
+    // takes ownership of the APersistableBundle*
+    PersistableBundle(APersistableBundle* _Nonnull bundle) noexcept : mPBundle(bundle) {}
+    // takes ownership of the APersistableBundle*
+    PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
+    // duplicates, does not take ownership of the APersistableBundle*
+    PersistableBundle(const PersistableBundle& other) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_dup(other.mPBundle);
+        }
+    }
+    // duplicates, does not take ownership of the APersistableBundle*
+    PersistableBundle& operator=(const PersistableBundle& other) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_dup(other.mPBundle);
+        }
+        return *this;
+    }
+
+    ~PersistableBundle() { reset(); }
+
+    binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+        reset();
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_readFromParcel(parcel, &mPBundle);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
+    }
+
+    binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+        if (!mPBundle) {
+            return STATUS_BAD_VALUE;
+        }
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_writeToParcel(mPBundle, parcel);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
+    }
+
+    /**
+     * Destroys any currently owned APersistableBundle* and takes ownership of the given
+     * APersistableBundle*
+     *
+     * @param pBundle The APersistableBundle to take ownership of
+     */
+    void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
+        if (mPBundle) {
+            if (__builtin_available(android __ANDROID_API_V__, *)) {
+                APersistableBundle_delete(mPBundle);
+            }
+            mPBundle = nullptr;
+        }
+        mPBundle = pBundle;
+    }
+
+    /**
+     * Check the actual contents of the bundle for equality. This is typically
+     * what should be used to check for equality.
+     */
+    bool deepEquals(const PersistableBundle& rhs) const {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_isEqual(get(), rhs.get());
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * NOTE: This does NOT check the contents of the PersistableBundle. This is
+     * implemented for ordering. Use deepEquals() to check for equality between
+     * two different PersistableBundle objects.
+     */
+    inline bool operator==(const PersistableBundle& rhs) const { return get() == rhs.get(); }
+    inline bool operator!=(const PersistableBundle& rhs) const { return get() != rhs.get(); }
+
+    inline bool operator<(const PersistableBundle& rhs) const { return get() < rhs.get(); }
+    inline bool operator>(const PersistableBundle& rhs) const { return get() > rhs.get(); }
+    inline bool operator>=(const PersistableBundle& rhs) const { return !(*this < rhs); }
+    inline bool operator<=(const PersistableBundle& rhs) const { return !(*this > rhs); }
+
+    PersistableBundle& operator=(PersistableBundle&& other) noexcept {
+        reset(other.release());
+        return *this;
+    }
+
+    /**
+     * Stops managing any contained APersistableBundle*, returning it to the caller. Ownership
+     * is released.
+     * @return APersistableBundle* or null if this was empty
+     */
+    [[nodiscard]] APersistableBundle* _Nullable release() noexcept {
+        APersistableBundle* _Nullable ret = mPBundle;
+        mPBundle = nullptr;
+        return ret;
+    }
+
+    inline std::string toString() const {
+        if (!mPBundle) {
+            return "<PersistableBundle: null>";
+        } else if (__builtin_available(android __ANDROID_API_V__, *)) {
+            std::ostringstream os;
+            os << "<PersistableBundle: ";
+            os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
+            os << " >";
+            return os.str();
+        }
+        return "<PersistableBundle (unknown)>";
+    }
+
+    int32_t size() const {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_size(mPBundle);
+        } else {
+            return 0;
+        }
+    }
+
+    int32_t erase(const std::string& key) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_erase(mPBundle, key.c_str());
+        } else {
+            return 0;
+        }
+    }
+
+    void putBoolean(const std::string& key, bool val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putInt(const std::string& key, int32_t val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putInt(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putLong(const std::string& key, int64_t val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putLong(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putDouble(const std::string& key, double val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putDouble(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putString(const std::string& key, const std::string& val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
+        }
+    }
+
+    void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            // std::vector<bool> has no ::data().
+            int32_t num = vec.size();
+            if (num > 0) {
+                bool* newVec = (bool*)malloc(num * sizeof(bool));
+                if (newVec) {
+                    for (int32_t i = 0; i < num; i++) {
+                        newVec[i] = vec[i];
+                    }
+                    APersistableBundle_putBooleanVector(mPBundle, key.c_str(), newVec, num);
+                    free(newVec);
+                }
+            }
+        }
+    }
+
+    void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                char** inVec = (char**)malloc(num * sizeof(char*));
+                if (inVec) {
+                    for (int32_t i = 0; i < num; i++) {
+                        inVec[i] = strdup(vec[i].c_str());
+                    }
+                    APersistableBundle_putStringVector(mPBundle, key.c_str(), inVec, num);
+                    free(inVec);
+                }
+            }
+        }
+    }
+    void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
+        }
+    }
+
+    bool getBoolean(const std::string& key, bool* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getInt(const std::string& key, int32_t* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getInt(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getLong(const std::string& key, int64_t* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getLong(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getDouble(const std::string& key, double* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    static char* _Nullable stringAllocator(int32_t bufferSizeBytes, void* _Nullable) {
+        return (char*)malloc(bufferSizeBytes);
+    }
+
+    bool getString(const std::string& key, std::string* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            char* outString = nullptr;
+            bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
+                                                    &stringAllocator, nullptr);
+            if (ret && outString) {
+                *val = std::string(outString);
+            }
+            return ret;
+        } else {
+            return false;
+        }
+    }
+
+    template <typename T>
+    bool getVecInternal(int32_t (*_Nonnull getVec)(const APersistableBundle* _Nonnull,
+                                                   const char* _Nonnull, T* _Nullable, int32_t),
+                        const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                        std::vector<T>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t bytes = 0;
+            // call first with nullptr to get required size in bytes
+            bytes = getVec(pBundle, key, nullptr, 0);
+            if (bytes > 0) {
+                T* newVec = (T*)malloc(bytes);
+                if (newVec) {
+                    bytes = getVec(pBundle, key, newVec, bytes);
+                    int32_t elements = bytes / sizeof(T);
+                    vec->clear();
+                    for (int32_t i = 0; i < elements; i++) {
+                        vec->push_back(newVec[i]);
+                    }
+                    free(newVec);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
+                                        vec);
+        }
+        return false;
+    }
+    bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
+                                           vec);
+        }
+        return false;
+    }
+    bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
+                                           vec);
+        }
+        return false;
+    }
+    bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
+                                          key.c_str(), vec);
+        }
+        return false;
+    }
+
+    // Takes ownership of and frees the char** and its elements.
+    // Creates a new set or vector based on the array of char*.
+    template <typename T>
+    T moveStringsInternal(char* _Nullable* _Nonnull strings, int32_t bufferSizeBytes) {
+        if (strings && bufferSizeBytes > 0) {
+            int32_t num = bufferSizeBytes / sizeof(char*);
+            T ret;
+            for (int32_t i = 0; i < num; i++) {
+                ret.insert(ret.end(), std::string(strings[i]));
+                free(strings[i]);
+            }
+            free(strings);
+            return ret;
+        }
+        return T();
+    }
+
+    bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
+                                                               &stringAllocator, nullptr);
+            if (bytes > 0) {
+                char** strings = (char**)malloc(bytes);
+                if (strings) {
+                    bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), strings,
+                                                               bytes, &stringAllocator, nullptr);
+                    *vec = moveStringsInternal<std::vector<std::string>>(strings, bytes);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle* bundle = nullptr;
+            bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
+            if (ret) {
+                *val = PersistableBundle(bundle);
+            }
+            return ret;
+        } else {
+            return false;
+        }
+    }
+
+    std::set<std::string> getKeys(
+            int32_t (*_Nonnull getTypedKeys)(const APersistableBundle* _Nonnull pBundle,
+                                             char* _Nullable* _Nullable outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* _Nullable),
+            const APersistableBundle* _Nonnull pBundle) {
+        // call first with nullptr to get required size in bytes
+        int32_t bytes = getTypedKeys(pBundle, nullptr, 0, &stringAllocator, nullptr);
+        if (bytes > 0) {
+            char** keys = (char**)malloc(bytes);
+            if (keys) {
+                bytes = getTypedKeys(pBundle, keys, bytes, &stringAllocator, nullptr);
+                return moveStringsInternal<std::set<std::string>>(keys, bytes);
+            }
+        }
+        return {};
+    }
+
+    std::set<std::string> getBooleanKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getIntKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getIntKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getLongKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getLongKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getDoubleKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getStringKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getStringKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getBooleanVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getIntVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getLongVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getDoubleVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getStringVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getPersistableBundleKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getMonKeys() {
+        // :P
+        return {"c(o,o)b", "c(o,o)b"};
+    }
+
+   private:
+    inline APersistableBundle* _Nullable get() const { return mPBundle; }
+    APersistableBundle* _Nullable mPBundle = nullptr;
+};
+
+}  // namespace aidl::android::os
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index db2d2c1..b1ab7b0 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -390,6 +390,12 @@
  * calling process dies and is replaced with another process with elevated permissions and the same
  * PID.
  *
+ * Warning: oneway transactions do not receive PID. Even if you expect
+ * a transaction to be synchronous, a misbehaving client could send it
+ * as a synchronous call and result in a 0 PID here. Additionally, if
+ * there is a race and the calling process dies, the PID may still be
+ * 0 for a synchronous call.
+ *
  * Available since API level 29.
  *
  * \return calling pid or the current process's PID if this thread isn't processing a transaction.
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_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
new file mode 100644
index 0000000..eff8104
--- /dev/null
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -0,0 +1,905 @@
+/*
+ * 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 <android/binder_parcel.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * A mapping from string keys to values of various types.
+ * See frameworks/base/core/java/android/os/PersistableBundle.java
+ * for the Java type than can be used in SDK APIs.
+ * APersistableBundle exists to be used in AIDL interfaces and seamlessly
+ * interact with framework services.
+ * frameworks/native/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+ * contains the AIDL type used in the ndk backend of AIDL interfaces.
+ */
+struct APersistableBundle;
+typedef struct APersistableBundle APersistableBundle;
+
+/**
+ * This is a user supplied allocator that allocates a buffer for the
+ * APersistableBundle APIs to fill in with a string.
+ *
+ * \param the required size in bytes for the allocated buffer
+ * \param  void* _Nullable context if needed by the callback
+ *
+ * \return allocated buffer of sizeBytes. Null if allocation failed.
+ */
+typedef char* _Nullable (*_Nonnull APersistableBundle_stringAllocator)(int32_t sizeBytes,
+                                                                       void* _Nullable context);
+
+/**
+ * Create a new APersistableBundle.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Create a new APersistableBundle based off an existing APersistableBundle.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to duplicate
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Delete an APersistableBundle. This must always be called when finished using
+ * the object.
+ *
+ * \param bundle to delete
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_delete(APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Check for equality of APersistableBundles.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param lhs bundle to compare agains the other param
+ * \param rhs bundle to compare agains the other param
+ *
+ * \return true when equal, false when not
+ */
+bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
+                                const APersistableBundle* _Nonnull rhs)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Read an APersistableBundle from an AParcel.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param parcel to read from
+ * \param outPBundle bundle to write to
+ *
+ * \return STATUS_OK on success
+ *         STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an
+ *                          issue deserializing (eg, corrupted parcel)
+ *         STATUS_BAD_TYPE if the parcel's current data position is not that of
+ *                         an APersistableBundle type
+ *         STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t APersistableBundle_readFromParcel(
+        const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Write an APersistableBundle to an AParcel.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param pBundle bundle to write to the parcel
+ * \param parcel to write to
+ *
+ * \return STATUS_OK on success.
+ *         STATUS_BAD_VALUE if either pBundle or parcel is null, or if the
+ *         APersistableBundle*
+ *                          fails to serialize (eg, internally corrupted)
+ *         STATUS_NO_MEMORY if the parcel runs out of space to store the pBundle & is
+ *                          unable to allocate more
+ *         STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
+                                                 AParcel* _Nonnull parcel)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get the size of an APersistableBundle. This is the number of mappings in the
+ * object.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to get the size of (number of mappings)
+ *
+ * \return number of mappings in the object
+ */
+int32_t APersistableBundle_size(APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Erase any entries added with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping to erase
+ *
+ * \return number of entries erased. Either 0 or 1.
+ */
+int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a boolean associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int32_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int64_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a double associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                  double val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a string associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a boolean vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
+                                         const char* _Nonnull key, const bool* _Nonnull vec,
+                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int32_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                     const int32_t* _Nonnull vec, int32_t num)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int64_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
+                                      const char* _Nonnull key, const int64_t* _Nonnull vec,
+                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a double vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key, const double* _Nonnull vec,
+                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a string vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key,
+                                        const char* _Nullable const* _Nullable vec, int32_t num)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an APersistableBundle associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
+                                             const char* _Nonnull key,
+                                             const APersistableBundle* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a boolean associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
+                                   const char* _Nonnull key, bool* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int32_t associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int64_t associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
+                                const char* _Nonnull key, int64_t* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a double associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
+                                  const char* _Nonnull key, double* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a string associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of string associated with the provided key on success
+ *         0 if no string exists for the provided key
+ *         -1 if the provided allocator fails and returns false
+ */
+int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
+                                     const char* _Nonnull key, char* _Nullable* _Nonnull val,
+                                     APersistableBundle_stringAllocator stringAllocator,
+                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a boolean vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
+                                            const char* _Nonnull key, bool* _Nullable buffer,
+                                            int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int32_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key, int32_t* _Nullable buffer,
+                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int64_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
+                                         const char* _Nonnull key, int64_t* _Nullable buffer,
+                                         int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a double vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
+                                           const char* _Nonnull key, double* _Nullable buffer,
+                                           int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a string vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pBundle,
+                                           const char* _Nonnull key,
+                                           char* _Nullable* _Nullable buffer,
+                                           int32_t bufferSizeBytes,
+                                           APersistableBundle_stringAllocator stringAllocator,
+                                           void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an APersistableBundle* associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to an APersistableBundle pointer to write to point to
+ * a new copy of the stored APersistableBundle. The caller takes ownership of
+ * the new APersistableBundle and must be deleted with
+ * APersistableBundle_delete.
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
+                                             const char* _Nonnull key,
+                                             APersistableBundle* _Nullable* _Nonnull outBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBundle,
+                                          char* _Nullable* _Nullable outKeys,
+                                          int32_t bufferSizeBytes,
+                                          APersistableBundle_stringAllocator stringAllocator,
+                                          void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
+                                      char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+                                      APersistableBundle_stringAllocator stringAllocator,
+                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
+                                       char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+                                       APersistableBundle_stringAllocator stringAllocator,
+                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* _Nonnull pBundle,
+                                         char* _Nullable* _Nullable outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* _Nonnull pBundle,
+                                         char* _Nullable* _Nullable outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                                char* _Nullable* _Nullable outKeys,
+                                                int32_t bufferSizeBytes,
+                                                APersistableBundle_stringAllocator stringAllocator,
+                                                void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                            char* _Nullable* _Nullable outKeys,
+                                            int32_t bufferSizeBytes,
+                                            APersistableBundle_stringAllocator stringAllocator,
+                                            void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                             char* _Nullable* _Nullable outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                               char* _Nullable* _Nullable outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                               char* _Nullable* _Nullable outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getPersistableBundleKeys(
+        const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
+        int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
+        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
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/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 1c5f79f..0843a8e 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -161,6 +161,51 @@
     AServiceManager_addServiceWithFlags; # systemapi llndk
 };
 
+LIBBINDER_NDK35 { # introduced=VanillaIceCream
+  global:
+    APersistableBundle_readFromParcel;
+    APersistableBundle_writeToParcel;
+    APersistableBundle_new;
+    APersistableBundle_dup;
+    APersistableBundle_delete;
+    APersistableBundle_isEqual;
+    APersistableBundle_size;
+    APersistableBundle_erase;
+    APersistableBundle_putBoolean;
+    APersistableBundle_putInt;
+    APersistableBundle_putLong;
+    APersistableBundle_putDouble;
+    APersistableBundle_putString;
+    APersistableBundle_putBooleanVector;
+    APersistableBundle_putIntVector;
+    APersistableBundle_putLongVector;
+    APersistableBundle_putDoubleVector;
+    APersistableBundle_putStringVector;
+    APersistableBundle_putPersistableBundle;
+    APersistableBundle_getBoolean;
+    APersistableBundle_getInt;
+    APersistableBundle_getLong;
+    APersistableBundle_getDouble;
+    APersistableBundle_getString;
+    APersistableBundle_getBooleanVector;
+    APersistableBundle_getIntVector;
+    APersistableBundle_getLongVector;
+    APersistableBundle_getDoubleVector;
+    APersistableBundle_getStringVector;
+    APersistableBundle_getPersistableBundle;
+    APersistableBundle_getBooleanKeys;
+    APersistableBundle_getIntKeys;
+    APersistableBundle_getLongKeys;
+    APersistableBundle_getDoubleKeys;
+    APersistableBundle_getStringKeys;
+    APersistableBundle_getBooleanVectorKeys;
+    APersistableBundle_getIntVectorKeys;
+    APersistableBundle_getLongVectorKeys;
+    APersistableBundle_getDoubleVectorKeys;
+    APersistableBundle_getStringVectorKeys;
+    APersistableBundle_getPersistableBundleKeys;
+};
+
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index 037aa2e..88ce5f4 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -16,24 +16,23 @@
 
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
-#include "parcel_internal.h"
-
-#include "ibinder_internal.h"
-#include "status_internal.h"
+#include <binder/Parcel.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <binder/unique_fd.h>
+#include <inttypes.h>
+#include <utils/Unicode.h>
 
 #include <limits>
 
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <binder/Parcel.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <utils/Unicode.h>
+#include "ibinder_internal.h"
+#include "parcel_internal.h"
+#include "status_internal.h"
 
 using ::android::IBinder;
 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>
@@ -52,11 +51,11 @@
     if (length < -1) return STATUS_BAD_VALUE;
 
     if (!isNullArray && length < 0) {
-        LOG(ERROR) << __func__ << ": non-null array but length is " << length;
+        ALOGE("non-null array but length is %" PRIi32, length);
         return STATUS_BAD_VALUE;
     }
     if (isNullArray && length > 0) {
-        LOG(ERROR) << __func__ << ": null buffer cannot be for size " << length << " array.";
+        ALOGE("null buffer cannot be for size %" PRIi32 " array.", length);
         return STATUS_BAD_VALUE;
     }
 
@@ -325,7 +324,7 @@
 binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t length) {
     if (string == nullptr) {
         if (length != -1) {
-            LOG(WARNING) << __func__ << ": null string must be used with length == -1.";
+            ALOGW("null string must be used with length == -1.");
             return STATUS_BAD_VALUE;
         }
 
@@ -334,7 +333,7 @@
     }
 
     if (length < 0) {
-        LOG(WARNING) << __func__ << ": Negative string length: " << length;
+        ALOGW("Negative string length: %" PRIi32, length);
         return STATUS_BAD_VALUE;
     }
 
@@ -342,7 +341,7 @@
     const ssize_t len16 = utf8_to_utf16_length(str8, length);
 
     if (len16 < 0 || len16 >= std::numeric_limits<int32_t>::max()) {
-        LOG(WARNING) << __func__ << ": Invalid string length: " << len16;
+        ALOGW("Invalid string length: %zd", len16);
         return STATUS_BAD_VALUE;
     }
 
@@ -383,7 +382,7 @@
     }
 
     if (len8 <= 0 || len8 > std::numeric_limits<int32_t>::max()) {
-        LOG(WARNING) << __func__ << ": Invalid string length: " << len8;
+        ALOGW("Invalid string length: %zd", len8);
         return STATUS_BAD_VALUE;
     }
 
@@ -391,7 +390,7 @@
     bool success = allocator(stringData, len8, &str8);
 
     if (!success || str8 == nullptr) {
-        LOG(WARNING) << __func__ << ": AParcel_stringAllocator failed to allocate.";
+        ALOGW("AParcel_stringAllocator failed to allocate.");
         return STATUS_NO_MEMORY;
     }
 
diff --git a/libs/binder/ndk/persistable_bundle.cpp b/libs/binder/ndk/persistable_bundle.cpp
new file mode 100644
index 0000000..404611c
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle.cpp
@@ -0,0 +1,313 @@
+/*
+ * 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 <android/binder_libbinder.h>
+#include <android/persistable_bundle.h>
+#include <binder/PersistableBundle.h>
+#include <log/log.h>
+#include <persistable_bundle_internal.h>
+#include <string.h>
+
+#include <set>
+
+__BEGIN_DECLS
+
+struct APersistableBundle {
+    APersistableBundle(const APersistableBundle& pBundle) : mPBundle(pBundle.mPBundle) {}
+    APersistableBundle(const android::os::PersistableBundle& pBundle) : mPBundle(pBundle) {}
+    APersistableBundle() = default;
+    android::os::PersistableBundle mPBundle;
+};
+
+APersistableBundle* _Nullable APersistableBundle_new() {
+    return new (std::nothrow) APersistableBundle();
+}
+
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* pBundle) {
+    if (pBundle) {
+        return new APersistableBundle(*pBundle);
+    } else {
+        return new APersistableBundle();
+    }
+}
+
+void APersistableBundle_delete(APersistableBundle* pBundle) {
+    free(pBundle);
+}
+
+bool APersistableBundle_isEqual(const APersistableBundle* lhs, const APersistableBundle* rhs) {
+    if (lhs && rhs) {
+        return lhs->mPBundle == rhs->mPBundle;
+    } else if (lhs == rhs) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+binder_status_t APersistableBundle_readFromParcel(const AParcel* parcel,
+                                                  APersistableBundle* _Nullable* outPBundle) {
+    if (!parcel || !outPBundle) return STATUS_BAD_VALUE;
+    APersistableBundle* newPBundle = APersistableBundle_new();
+    if (newPBundle == nullptr) return STATUS_NO_MEMORY;
+    binder_status_t status =
+            newPBundle->mPBundle.readFromParcel(AParcel_viewPlatformParcel(parcel));
+    if (status == STATUS_OK) {
+        *outPBundle = newPBundle;
+    }
+    return status;
+}
+
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* pBundle,
+                                                 AParcel* parcel) {
+    if (!parcel || !pBundle) return STATUS_BAD_VALUE;
+    return pBundle->mPBundle.writeToParcel(AParcel_viewPlatformParcel(parcel));
+}
+
+int32_t APersistableBundle_size(APersistableBundle* pBundle) {
+    size_t size = pBundle->mPBundle.size();
+    LOG_ALWAYS_FATAL_IF(size > INT32_MAX,
+                        "The APersistableBundle has gotten too large! There will be an overflow in "
+                        "the reported size.");
+    return pBundle->mPBundle.size();
+}
+int32_t APersistableBundle_erase(APersistableBundle* pBundle, const char* key) {
+    return pBundle->mPBundle.erase(android::String16(key));
+}
+void APersistableBundle_putBoolean(APersistableBundle* pBundle, const char* key, bool val) {
+    pBundle->mPBundle.putBoolean(android::String16(key), val);
+}
+void APersistableBundle_putInt(APersistableBundle* pBundle, const char* key, int32_t val) {
+    pBundle->mPBundle.putInt(android::String16(key), val);
+}
+void APersistableBundle_putLong(APersistableBundle* pBundle, const char* key, int64_t val) {
+    pBundle->mPBundle.putLong(android::String16(key), val);
+}
+void APersistableBundle_putDouble(APersistableBundle* pBundle, const char* key, double val) {
+    pBundle->mPBundle.putDouble(android::String16(key), val);
+}
+void APersistableBundle_putString(APersistableBundle* pBundle, const char* key, const char* val) {
+    pBundle->mPBundle.putString(android::String16(key), android::String16(val));
+}
+void APersistableBundle_putBooleanVector(APersistableBundle* pBundle, const char* key,
+                                         const bool* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<bool> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putBooleanVector(android::String16(key), newVec);
+}
+void APersistableBundle_putIntVector(APersistableBundle* pBundle, const char* key,
+                                     const int32_t* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<int32_t> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putIntVector(android::String16(key), newVec);
+}
+void APersistableBundle_putLongVector(APersistableBundle* pBundle, const char* key,
+                                      const int64_t* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<int64_t> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putLongVector(android::String16(key), newVec);
+}
+void APersistableBundle_putDoubleVector(APersistableBundle* pBundle, const char* key,
+                                        const double* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<double> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putDoubleVector(android::String16(key), newVec);
+}
+void APersistableBundle_putStringVector(APersistableBundle* pBundle, const char* key,
+                                        const char* const* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<android::String16> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = android::String16(vec[i]);
+    }
+    pBundle->mPBundle.putStringVector(android::String16(key), newVec);
+}
+void APersistableBundle_putPersistableBundle(APersistableBundle* pBundle, const char* key,
+                                             const APersistableBundle* val) {
+    pBundle->mPBundle.putPersistableBundle(android::String16(key), val->mPBundle);
+}
+bool APersistableBundle_getBoolean(const APersistableBundle* pBundle, const char* key, bool* val) {
+    return pBundle->mPBundle.getBoolean(android::String16(key), val);
+}
+bool APersistableBundle_getInt(const APersistableBundle* pBundle, const char* key, int32_t* val) {
+    return pBundle->mPBundle.getInt(android::String16(key), val);
+}
+bool APersistableBundle_getLong(const APersistableBundle* pBundle, const char* key, int64_t* val) {
+    return pBundle->mPBundle.getLong(android::String16(key), val);
+}
+bool APersistableBundle_getDouble(const APersistableBundle* pBundle, const char* key, double* val) {
+    return pBundle->mPBundle.getDouble(android::String16(key), val);
+}
+int32_t APersistableBundle_getString(const APersistableBundle* pBundle, const char* key, char** val,
+                                     APersistableBundle_stringAllocator stringAllocator,
+                                     void* context) {
+    android::String16 outVal;
+    bool ret = pBundle->mPBundle.getString(android::String16(key), &outVal);
+    if (ret) {
+        android::String8 tmp8(outVal);
+        *val = stringAllocator(tmp8.bytes() + 1, context);
+        if (*val) {
+            strncpy(*val, tmp8.c_str(), tmp8.bytes() + 1);
+            return tmp8.bytes();
+        } else {
+            return -1;
+        }
+    }
+    return 0;
+}
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* pBundle, const char* key,
+                                            bool* buffer, int32_t bufferSizeBytes) {
+    std::vector<bool> newVec;
+    pBundle->mPBundle.getBooleanVector(android::String16(key), &newVec);
+    return getVecInternal<bool>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getIntVector(const APersistableBundle* pBundle, const char* key,
+                                        int32_t* buffer, int32_t bufferSizeBytes) {
+    std::vector<int32_t> newVec;
+    pBundle->mPBundle.getIntVector(android::String16(key), &newVec);
+    return getVecInternal<int32_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getLongVector(const APersistableBundle* pBundle, const char* key,
+                                         int64_t* buffer, int32_t bufferSizeBytes) {
+    std::vector<int64_t> newVec;
+    pBundle->mPBundle.getLongVector(android::String16(key), &newVec);
+    return getVecInternal<int64_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* pBundle, const char* key,
+                                           double* buffer, int32_t bufferSizeBytes) {
+    std::vector<double> newVec;
+    pBundle->mPBundle.getDoubleVector(android::String16(key), &newVec);
+    return getVecInternal<double>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getStringVector(const APersistableBundle* pBundle, const char* key,
+                                           char** vec, int32_t bufferSizeBytes,
+                                           APersistableBundle_stringAllocator stringAllocator,
+                                           void* context) {
+    std::vector<android::String16> newVec;
+    pBundle->mPBundle.getStringVector(android::String16(key), &newVec);
+    return getStringsInternal<std::vector<android::String16>>(newVec, vec, bufferSizeBytes,
+                                                              stringAllocator, context);
+}
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* pBundle, const char* key,
+                                             APersistableBundle** outBundle) {
+    APersistableBundle* bundle = APersistableBundle_new();
+    bool ret = pBundle->mPBundle.getPersistableBundle(android::String16(key), &bundle->mPBundle);
+    if (ret) {
+        *outBundle = bundle;
+        return true;
+    }
+    return false;
+}
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* pBundle, char** outKeys,
+                                          int32_t bufferSizeBytes,
+                                          APersistableBundle_stringAllocator stringAllocator,
+                                          void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getBooleanKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* pBundle, char** outKeys,
+                                      int32_t bufferSizeBytes,
+                                      APersistableBundle_stringAllocator stringAllocator,
+                                      void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getIntKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* pBundle, char** outKeys,
+                                       int32_t bufferSizeBytes,
+                                       APersistableBundle_stringAllocator stringAllocator,
+                                       void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getLongKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* pBundle, char** outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getDoubleKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* pBundle, char** outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getStringKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                                int32_t bufferSizeBytes,
+                                                APersistableBundle_stringAllocator stringAllocator,
+                                                void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getBooleanVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                            int32_t bufferSizeBytes,
+                                            APersistableBundle_stringAllocator stringAllocator,
+                                            void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getIntVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getLongVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getDoubleVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getStringVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getPersistableBundleKeys(
+        const APersistableBundle* pBundle, char** outKeys, int32_t bufferSizeBytes,
+        APersistableBundle_stringAllocator stringAllocator, void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getPersistableBundleKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+
+__END_DECLS
diff --git a/libs/binder/ndk/persistable_bundle_internal.h b/libs/binder/ndk/persistable_bundle_internal.h
new file mode 100644
index 0000000..279c66f
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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 <android/persistable_bundle.h>
+#include <log/log.h>
+#include <utils/String8.h>
+
+//  take a vector and put the contents into a buffer.
+//  return the size of the contents.
+//  This may not put all of the contents into the buffer if the buffer is not
+//  large enough.
+template <typename T>
+int32_t getVecInternal(const std::vector<T>& inVec, T* _Nullable buffer, int32_t bufferSizeBytes) {
+    LOG_ALWAYS_FATAL_IF(inVec.size() > INT32_MAX,
+                        "The size of the APersistableBundle has gotten too large!");
+    LOG_ALWAYS_FATAL_IF(
+            bufferSizeBytes < 0,
+            "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+    int32_t num = inVec.size();
+    int32_t numAvailable = bufferSizeBytes / sizeof(T);
+    int32_t numFill = numAvailable < num ? numAvailable : num;
+
+    if (numFill > 0 && buffer) {
+        for (int32_t i = 0; i < numFill; i++) {
+            buffer[i] = inVec[i];
+        }
+    }
+    return num * sizeof(T);
+}
+
+//  take a vector or a set of String16 and put the contents into a char** buffer.
+//  return the size of the contents.
+//  This may not put all of the contents into the buffer if the buffer is not
+//  large enough.
+//  The strings are duped with a user supplied callback
+template <typename T>
+int32_t getStringsInternal(const T& strings, char* _Nullable* _Nullable buffer,
+                           int32_t bufferSizeBytes,
+                           APersistableBundle_stringAllocator stringAllocator,
+                           void* _Nullable context) {
+    LOG_ALWAYS_FATAL_IF(strings.size() > INT32_MAX,
+                        "The size of the APersistableBundle has gotten too large!");
+    LOG_ALWAYS_FATAL_IF(
+            bufferSizeBytes < 0,
+            "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+    int32_t num = strings.size();
+    int32_t numAvailable = bufferSizeBytes / sizeof(char*);
+    int32_t numFill = numAvailable < num ? numAvailable : num;
+    if (!stringAllocator) {
+        return -1;
+    }
+
+    if (numFill > 0 && buffer) {
+        int32_t i = 0;
+        for (const auto& val : strings) {
+            android::String8 tmp8 = android::String8(val);
+            buffer[i] = stringAllocator(tmp8.bytes() + 1, context);
+            if (buffer[i] == nullptr) {
+                return -1;
+            }
+            strncpy(buffer[i], tmp8.c_str(), tmp8.bytes() + 1);
+            i++;
+            if (i > numFill - 1) {
+                // buffer is too small to keep going or this is the end of the
+                // set
+                break;
+            }
+        }
+    }
+    return num * sizeof(char*);
+}
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index 0fea57b..0072ac3 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -15,12 +15,10 @@
  */
 
 #include <android/binder_process.h>
+#include <binder/IPCThreadState.h>
 
 #include <mutex>
 
-#include <android-base/logging.h>
-#include <binder/IPCThreadState.h>
-
 using ::android::IPCThreadState;
 using ::android::ProcessState;
 
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 2977786..3bfdc59 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -15,14 +15,12 @@
  */
 
 #include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <binder/LazyServiceRegistrar.h>
 
 #include "ibinder_internal.h"
 #include "status_internal.h"
 
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <binder/LazyServiceRegistrar.h>
-
 using ::android::defaultServiceManager;
 using ::android::IBinder;
 using ::android::IServiceManager;
@@ -115,7 +113,8 @@
         std::lock_guard<std::mutex> l(m);
         if (onRegister == nullptr) return;
 
-        CHECK_EQ(String8(smInstance), instance);
+        LOG_ALWAYS_FATAL_IF(String8(smInstance) != instance, "onServiceRegistration: %s != %s",
+                            String8(smInstance).c_str(), instance);
 
         sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
         AIBinder_incStrong(ret.get());
@@ -135,8 +134,8 @@
 AServiceManager_registerForServiceNotifications(const char* instance,
                                                 AServiceManager_onRegister onRegister,
                                                 void* cookie) {
-    CHECK_NE(instance, nullptr);
-    CHECK_NE(onRegister, nullptr) << instance;
+    LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
+    LOG_ALWAYS_FATAL_IF(onRegister == nullptr, "onRegister == nullptr for %s", instance);
     // cookie can be nullptr
 
     auto cb = sp<AServiceManager_NotificationRegistration>::make();
@@ -146,8 +145,8 @@
 
     sp<IServiceManager> sm = defaultServiceManager();
     if (status_t res = sm->registerForNotifications(String16(instance), cb); res != STATUS_OK) {
-        LOG(ERROR) << "Failed to register for service notifications for " << instance << ": "
-                   << statusToString(res);
+        ALOGE("Failed to register for service notifications for %s: %s", instance,
+              statusToString(res).c_str());
         return nullptr;
     }
 
@@ -157,7 +156,7 @@
 
 void AServiceManager_NotificationRegistration_delete(
         AServiceManager_NotificationRegistration* notification) {
-    CHECK_NE(notification, nullptr);
+    LOG_ALWAYS_FATAL_IF(notification == nullptr, "notification == nullptr");
     notification->clear();
     notification->decStrong(nullptr);
 }
@@ -172,9 +171,9 @@
 }
 void AServiceManager_forEachDeclaredInstance(const char* interface, void* context,
                                              void (*callback)(const char*, void*)) {
-    CHECK(interface != nullptr);
+    LOG_ALWAYS_FATAL_IF(interface == nullptr, "interface == nullptr");
     // context may be nullptr
-    CHECK(callback != nullptr);
+    LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
 
     sp<IServiceManager> sm = defaultServiceManager();
     for (const String16& instance : sm->getDeclaredInstances(String16(interface))) {
@@ -191,9 +190,9 @@
 }
 void AServiceManager_getUpdatableApexName(const char* instance, void* context,
                                           void (*callback)(const char*, void*)) {
-    CHECK_NE(instance, nullptr);
+    LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
     // context may be nullptr
-    CHECK_NE(callback, nullptr);
+    LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
 
     sp<IServiceManager> sm = defaultServiceManager();
     std::optional<String16> updatableViaApex = sm->updatableViaApex(String16(instance));
diff --git a/libs/binder/ndk/stability.cpp b/libs/binder/ndk/stability.cpp
index 7eafb9c..ca3d5e6 100644
--- a/libs/binder/ndk/stability.cpp
+++ b/libs/binder/ndk/stability.cpp
@@ -27,6 +27,10 @@
 #error libbinder_ndk should only be built in a system context
 #endif
 
+#if defined(__ANDROID_VENDOR__) && !defined(__TRUSTY__)
+#error libbinder_ndk should only be built in a system context
+#endif
+
 #ifdef __ANDROID_NDK__
 #error libbinder_ndk should only be built in a system context
 #endif
diff --git a/libs/binder/ndk/status.cpp b/libs/binder/ndk/status.cpp
index 8ed91a5..3aac3c0 100644
--- a/libs/binder/ndk/status.cpp
+++ b/libs/binder/ndk/status.cpp
@@ -17,8 +17,6 @@
 #include <android/binder_status.h>
 #include "status_internal.h"
 
-#include <android-base/logging.h>
-
 using ::android::status_t;
 using ::android::statusToString;
 using ::android::binder::Status;
@@ -127,8 +125,8 @@
             return STATUS_UNKNOWN_ERROR;
 
         default:
-            LOG(WARNING) << __func__ << ": Unknown status_t (" << statusToString(status)
-                         << ") pruned into STATUS_UNKNOWN_ERROR";
+            ALOGW("%s: Unknown status_t (%s) pruned into STATUS_UNKNOWN_ERROR", __func__,
+                  statusToString(status).c_str());
             return STATUS_UNKNOWN_ERROR;
     }
 }
@@ -159,8 +157,8 @@
             return EX_TRANSACTION_FAILED;
 
         default:
-            LOG(WARNING) << __func__ << ": Unknown binder exception (" << exception
-                         << ") pruned into EX_TRANSACTION_FAILED";
+            ALOGW("%s: Unknown binder exception (%d) pruned into EX_TRANSACTION_FAILED", __func__,
+                  exception);
             return EX_TRANSACTION_FAILED;
     }
 }
diff --git a/libs/binder/rust/libbinder_ndk_bindgen_flags.txt b/libs/binder/rust/libbinder_ndk_bindgen_flags.txt
index 551c59f..cb6993e 100644
--- a/libs/binder/rust/libbinder_ndk_bindgen_flags.txt
+++ b/libs/binder/rust/libbinder_ndk_bindgen_flags.txt
@@ -8,4 +8,17 @@
 --allowlist-type=AIBinder_DeathRecipient
 --allowlist-type=AParcel
 --allowlist-type=binder_status_t
+--blocklist-function="vprintf"
+--blocklist-function="strtold"
+--blocklist-function="_vtlog"
+--blocklist-function="vscanf"
+--blocklist-function="vfprintf_worker"
+--blocklist-function="vsprintf"
+--blocklist-function="vsnprintf"
+--blocklist-function="vsnprintf_filtered"
+--blocklist-function="vfscanf"
+--blocklist-function="vsscanf"
+--blocklist-function="vdprintf"
+--blocklist-function="vasprintf"
+--blocklist-function="strtold_l"
 --allowlist-function=.*
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/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index a957385..163f000 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,8 +16,10 @@
 
 //! API for RPC Binder services.
 
+#[cfg(not(target_os = "trusty"))]
 mod server;
 mod session;
 
+#[cfg(not(target_os = "trusty"))]
 pub use server::{RpcServer, RpcServerRef};
 pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs
index 79a9510..09688a2 100644
--- a/libs/binder/rust/rpcbinder/src/session.rs
+++ b/libs/binder/rust/rpcbinder/src/session.rs
@@ -17,11 +17,8 @@
 use binder::unstable_api::new_spibinder;
 use binder::{FromIBinder, SpIBinder, StatusCode, Strong};
 use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
-use std::os::{
-    raw::{c_int, c_void},
-    unix::io::{AsRawFd, BorrowedFd, RawFd},
-};
+use std::os::fd::RawFd;
+use std::os::raw::{c_int, c_void};
 
 pub use binder_rpc_unstable_bindgen::ARpcSession_FileDescriptorTransportMode as FileDescriptorTransportMode;
 
@@ -87,6 +84,7 @@
     }
 
     /// Connects to an RPC Binder server over vsock for a particular interface.
+    #[cfg(not(target_os = "trusty"))]
     pub fn setup_vsock_client<T: FromIBinder + ?Sized>(
         &self,
         cid: u32,
@@ -106,11 +104,12 @@
 
     /// Connects to an RPC Binder server over a names Unix Domain Socket for
     /// a particular interface.
+    #[cfg(not(target_os = "trusty"))]
     pub fn setup_unix_domain_client<T: FromIBinder + ?Sized>(
         &self,
         socket_name: &str,
     ) -> Result<Strong<T>, StatusCode> {
-        let socket_name = match CString::new(socket_name) {
+        let socket_name = match std::ffi::CString::new(socket_name) {
             Ok(s) => s,
             Err(e) => {
                 log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
@@ -131,10 +130,12 @@
 
     /// Connects to an RPC Binder server over a bootstrap Unix Domain Socket
     /// for a particular interface.
+    #[cfg(not(target_os = "trusty"))]
     pub fn setup_unix_domain_bootstrap_client<T: FromIBinder + ?Sized>(
         &self,
-        bootstrap_fd: BorrowedFd,
+        bootstrap_fd: std::os::fd::BorrowedFd,
     ) -> Result<Strong<T>, StatusCode> {
+        use std::os::fd::AsRawFd;
         // SAFETY: ARpcSession_setupUnixDomainBootstrapClient does not take
         // ownership of bootstrap_fd. The returned AIBinder has correct
         // reference count, and the ownership can safely be taken by new_spibinder.
@@ -148,12 +149,13 @@
     }
 
     /// Connects to an RPC Binder server over inet socket at the given address and port.
+    #[cfg(not(target_os = "trusty"))]
     pub fn setup_inet_client<T: FromIBinder + ?Sized>(
         &self,
         address: &str,
         port: u32,
     ) -> Result<Strong<T>, StatusCode> {
-        let address = match CString::new(address) {
+        let address = match std::ffi::CString::new(address) {
             Ok(s) => s,
             Err(e) => {
                 log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
@@ -173,6 +175,22 @@
         Self::get_interface(service)
     }
 
+    #[cfg(target_os = "trusty")]
+    pub fn setup_trusty_client<T: FromIBinder + ?Sized>(
+        &self,
+        port: &std::ffi::CStr,
+    ) -> Result<Strong<T>, StatusCode> {
+        self.setup_preconnected_client(|| {
+            let h = tipc::Handle::connect(port)
+                .expect("Failed to connect to service port {SERVICE_PORT}");
+
+            // Do not close the handle at the end of the scope
+            let fd = h.as_raw_fd();
+            core::mem::forget(h);
+            Some(fd)
+        })
+    }
+
     /// Connects to an RPC Binder server, using the given callback to get (and
     /// take ownership of) file descriptors already connected to it.
     pub fn setup_preconnected_client<T: FromIBinder + ?Sized>(
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index a08cb7a..e34d31e 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -21,16 +21,17 @@
 use crate::proxy::{DeathRecipient, SpIBinder, WpIBinder};
 use crate::sys;
 
+use downcast_rs::{impl_downcast, DowncastSync};
 use std::borrow::Borrow;
 use std::cmp::Ordering;
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fmt;
-use std::fs::File;
+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.
@@ -51,7 +52,7 @@
 /// interfaces) must implement this trait.
 ///
 /// This is equivalent `IInterface` in C++.
-pub trait Interface: Send + Sync {
+pub trait Interface: Send + Sync + DowncastSync {
     /// Convert this binder object into a generic [`SpIBinder`] reference.
     fn as_binder(&self) -> SpIBinder {
         panic!("This object was not a Binder object and cannot be converted into an SpIBinder.")
@@ -61,11 +62,13 @@
     ///
     /// This handler is a no-op by default and should be implemented for each
     /// Binder service struct that wishes to respond to dump transactions.
-    fn dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 }
 
+impl_downcast!(sync Interface);
+
 /// Implemented by sync interfaces to specify what the associated async interface is.
 /// Generic to handle the fact that async interfaces are generic over a thread pool.
 ///
@@ -143,7 +146,7 @@
 /// When using the AIDL backend, users need only implement the high-level AIDL-defined
 /// interface. The AIDL compiler then generates a container struct that wraps
 /// the user-defined service and implements `Remotable`.
-pub trait Remotable: Send + Sync {
+pub trait Remotable: Send + Sync + 'static {
     /// The Binder interface descriptor string.
     ///
     /// This string is a unique identifier for a Binder interface, and should be
@@ -162,7 +165,7 @@
 
     /// Handle a request to invoke the dump transaction on this
     /// object.
-    fn on_dump(&self, file: &File, args: &[&CStr]) -> Result<()>;
+    fn on_dump(&self, file: &mut dyn Write, args: &[&CStr]) -> Result<()>;
 
     /// Retrieve the class of this remote object.
     ///
@@ -893,6 +896,23 @@
                 $crate::binder_impl::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
                 $crate::Strong::new(Box::new(binder))
             }
+
+            /// Tries to downcast the interface to another type.
+            /// When receiving this object from a binder call, make sure that the object received is
+            /// a binder native object and that is of the right type for the Downcast:
+            ///
+            /// let binder = received_object.as_binder();
+            /// if !binder.is_remote() {
+            ///     let binder_native: Binder<BnFoo> = binder.try_into()?;
+            ///     let original_object = binder_native.downcast_binder::<MyFoo>();
+            ///     // Check that returned type is not None before using it
+            /// }
+            ///
+            /// Handle the error cases instead of just calling `unwrap` or `expect` to prevent a
+            /// malicious caller to mount a Denial of Service attack.
+            pub fn downcast_binder<T: $interface>(&self) -> Option<&T> {
+                self.0.as_any().downcast_ref::<T>()
+            }
         }
 
         impl $crate::binder_impl::Remotable for $native {
@@ -914,8 +934,8 @@
                 }
             }
 
-            fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
-                self.0.dump(file, args)
+            fn on_dump(&self, writer: &mut dyn std::io::Write, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
+                self.0.dump(writer, args)
             }
 
             fn get_class() -> $crate::binder_impl::InterfaceClass {
@@ -1004,7 +1024,7 @@
 
         $(
         // Async interface trait implementations.
-        impl<P: $crate::BinderAsyncPool> $crate::FromIBinder for dyn $async_interface<P> {
+        impl<P: $crate::BinderAsyncPool + 'static> $crate::FromIBinder for dyn $async_interface<P> {
             fn try_from(mut ibinder: $crate::SpIBinder) -> std::result::Result<$crate::Strong<dyn $async_interface<P>>, $crate::StatusCode> {
                 use $crate::binder_impl::AssociateClass;
 
@@ -1030,27 +1050,27 @@
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::Serialize for dyn $async_interface<P> + '_ {
+        impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::Serialize for dyn $async_interface<P> + '_ {
             fn serialize(&self, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 let binder = $crate::Interface::as_binder(self);
                 parcel.write(&binder)
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::SerializeOption for dyn $async_interface<P> + '_ {
+        impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::SerializeOption for dyn $async_interface<P> + '_ {
             fn serialize_option(this: Option<&Self>, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 parcel.write(&this.map($crate::Interface::as_binder))
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> std::fmt::Debug for dyn $async_interface<P> + '_ {
+        impl<P: $crate::BinderAsyncPool + 'static> std::fmt::Debug for dyn $async_interface<P> + '_ {
             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                 f.pad(stringify!($async_interface))
             }
         }
 
         /// Convert a &dyn $async_interface to Strong<dyn $async_interface>
-        impl<P: $crate::BinderAsyncPool> std::borrow::ToOwned for dyn $async_interface<P> {
+        impl<P: $crate::BinderAsyncPool + 'static> std::borrow::ToOwned for dyn $async_interface<P> {
             type Owned = $crate::Strong<dyn $async_interface<P>>;
             fn to_owned(&self) -> Self::Owned {
                 self.as_binder().into_interface()
@@ -1058,11 +1078,11 @@
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToAsyncInterface<P> for dyn $interface {
+        impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::ToAsyncInterface<P> for dyn $interface {
             type Target = dyn $async_interface<P>;
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToSyncInterface for dyn $async_interface<P> {
+        impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::ToSyncInterface for dyn $async_interface<P> {
             type Target = dyn $interface;
         }
         )?
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 b248f5e..8ae010e 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -24,12 +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.
@@ -330,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,
@@ -339,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 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;
@@ -353,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()
@@ -366,13 +366,26 @@
         // Safety: Our caller promised that the binder has a `T` pointer in its
         // user data.
         let binder: &T = unsafe { &*(object as *const T) };
-        let res = binder.on_dump(&file, &args);
+        let res = binder.on_dump(&mut *file, &args);
 
         match res {
             Ok(()) => 0,
             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> {
@@ -569,7 +582,7 @@
         Ok(())
     }
 
-    fn on_dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn on_dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 
diff --git a/libs/binder/rust/src/parcel/file_descriptor.rs b/libs/binder/rust/src/parcel/file_descriptor.rs
index 5c688fa..6afe5ab 100644
--- a/libs/binder/rust/src/parcel/file_descriptor.rs
+++ b/libs/binder/rust/src/parcel/file_descriptor.rs
@@ -22,29 +22,28 @@
 use crate::error::{status_result, Result, StatusCode};
 use crate::sys;
 
-use std::fs::File;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 
 /// Rust version of the Java class android.os.ParcelFileDescriptor
 #[derive(Debug)]
-pub struct ParcelFileDescriptor(File);
+pub struct ParcelFileDescriptor(OwnedFd);
 
 impl ParcelFileDescriptor {
     /// Create a new `ParcelFileDescriptor`
-    pub fn new(file: File) -> Self {
-        Self(file)
+    pub fn new<F: Into<OwnedFd>>(fd: F) -> Self {
+        Self(fd.into())
     }
 }
 
-impl AsRef<File> for ParcelFileDescriptor {
-    fn as_ref(&self) -> &File {
+impl AsRef<OwnedFd> for ParcelFileDescriptor {
+    fn as_ref(&self) -> &OwnedFd {
         &self.0
     }
 }
 
-impl From<ParcelFileDescriptor> for File {
-    fn from(file: ParcelFileDescriptor) -> File {
-        file.0
+impl From<ParcelFileDescriptor> for OwnedFd {
+    fn from(fd: ParcelFileDescriptor) -> OwnedFd {
+        fd.0
     }
 }
 
@@ -120,7 +119,7 @@
             // Safety: At this point, we know that the file descriptor was
             // not -1, so must be a valid, owned file descriptor which we
             // can safely turn into a `File`.
-            let file = unsafe { File::from_raw_fd(fd) };
+            let file = unsafe { OwnedFd::from_raw_fd(fd) };
             Ok(Some(ParcelFileDescriptor::new(file)))
         }
     }
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/src/state.rs b/libs/binder/rust/src/state.rs
index a3a2562..8a06274 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -101,13 +101,16 @@
     /// dies and is replaced with another process with elevated permissions and
     /// the same PID.
     ///
+    /// Warning: oneway transactions do not receive PID. Even if you expect
+    /// a transaction to be synchronous, a misbehaving client could send it
+    /// as a synchronous call and result in a 0 PID here. Additionally, if
+    /// there is a race and the calling process dies, the PID may still be
+    /// 0 for a synchronous call.
+    ///
     /// Available since API level 29.
     ///
     /// \return calling pid or the current process's PID if this thread isn't
     /// processing a transaction.
-    ///
-    /// If the transaction being processed is a oneway transaction, then this
-    /// method will return 0.
     pub fn get_calling_pid() -> pid_t {
         // Safety: Safe FFI
         unsafe { sys::AIBinder_getCallingPid() }
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index c049b80..c87fa89 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -26,7 +26,7 @@
 
 use std::convert::{TryFrom, TryInto};
 use std::ffi::CStr;
-use std::fs::File;
+use std::io::Write;
 use std::sync::Mutex;
 
 /// Name of service runner.
@@ -118,7 +118,7 @@
 }
 
 impl Interface for TestService {
-    fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
+    fn dump(&self, _writer: &mut dyn Write, args: &[&CStr]) -> Result<(), StatusCode> {
         let mut dump_args = self.dump_args.lock().unwrap();
         dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
         Ok(())
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
index 37f182e..fbedfee 100644
--- a/libs/binder/rust/tests/ndk_rust_interop.rs
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -58,7 +58,7 @@
     let wrong_service: Result<binder::Strong<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
         binder::get_interface(service_name);
     match wrong_service {
-        Err(e) if e == StatusCode::BAD_TYPE => {}
+        Err(StatusCode::BAD_TYPE) => {}
         Err(e) => {
             eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
             return e as c_int;
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 5bf9680..18b178b 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -17,12 +17,11 @@
 #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-base/stringprintf.h>
 #include <android/debug/BnAdbCallback.h>
 #include <android/debug/IAdbManager.h>
 #include <android/os/BnServiceManager.h>
@@ -31,6 +30,8 @@
 #include <binder/ProcessState.h>
 #include <binder/RpcServer.h>
 
+#include "file.h"
+
 using android::BBinder;
 using android::defaultServiceManager;
 using android::OK;
@@ -39,14 +40,12 @@
 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;
 using android::base::LogId;
 using android::base::LogSeverity;
 using android::base::StdioLogger;
-using android::base::StringPrintf;
 using std::string_view_literals::operator""sv;
 
 namespace {
@@ -55,13 +54,14 @@
 using ServiceRetriever = decltype(&android::IServiceManager::checkService);
 using android::debug::IAdbManager;
 
-int Usage(const char* program) {
-    auto basename = Basename(program);
-    auto format = R"(dispatch calls to RPC service.
+int Usage(std::filesystem::path program) {
+    auto basename = program.filename();
+    // clang-format off
+    LOG(ERROR) << R"(dispatch calls to RPC service.
 Usage:
-  %s [-g] [-i <ip_address>] <service_name>
+  )" << basename << R"( [-g] [-i <ip_address>] <service_name>
     <service_name>: the service to connect to.
-  %s [-g] manager
+  )" << basename << R"( [-g] manager
     Runs an RPC-friendly service that redirects calls to servicemanager.
 
   -g: use getService() instead of checkService().
@@ -71,7 +71,7 @@
   blocks until killed.
   Otherwise, writes error message to stderr and exits with non-zero code.
 )";
-    LOG(ERROR) << StringPrintf(format, basename.c_str(), basename.c_str());
+    // clang-format on
     return EX_USAGE;
 }
 
@@ -254,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..dd2be94 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",
@@ -227,6 +256,12 @@
     // contention on the device. b/276820894
     test_options: {
         unit_test: false,
+        test_runner_options: [
+            {
+                name: "native-test-timeout",
+                value: "10m",
+            },
+        ],
     },
 
     test_suites: ["general-tests"],
@@ -801,6 +836,7 @@
         ],
         // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
         hotlists: ["4637097"],
+        use_for_presubmit: true,
     },
 }
 
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/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
index 6712c9c..7e0b594 100644
--- a/libs/binder/tests/binderAllocationLimits.cpp
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -16,6 +16,7 @@
 
 #include <android-base/logging.h>
 #include <binder/Binder.h>
+#include <binder/Functional.h>
 #include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
@@ -28,6 +29,8 @@
 #include <functional>
 #include <vector>
 
+using namespace android::binder::impl;
+
 static android::String8 gEmpty(""); // make sure first allocation from optimization runs
 
 struct DestructionAction {
@@ -172,6 +175,18 @@
     a_binder->pingBinder();
 }
 
+TEST(BinderAllocation, MakeScopeGuard) {
+    const auto m = ScopeDisallowMalloc();
+    {
+        auto guard1 = make_scope_guard([] {});
+        guard1.release();
+
+        auto guard2 = make_scope_guard([&guard1, ptr = imaginary_use] {
+            if (ptr == nullptr) guard1.release();
+        });
+    }
+}
+
 TEST(BinderAllocation, InterfaceDescriptorTransaction) {
     sp<IBinder> a_binder = GetRemoteBinder();
 
diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp
index 0075688..0ae536c 100644
--- a/libs/binder/tests/binderHostDeviceTest.cpp
+++ b/libs/binder/tests/binderHostDeviceTest.cpp
@@ -75,7 +75,7 @@
 public:
     void SetUp() override {
         auto debuggableResult = execute(Split("adb shell getprop ro.debuggable", " "), nullptr);
-        ASSERT_THAT(debuggableResult, Ok());
+        ASSERT_TRUE(debuggableResult.has_value());
         ASSERT_EQ(0, debuggableResult->exitCode) << *debuggableResult;
         auto debuggableBool = ParseBool(Trim(debuggableResult->stdoutStr));
         ASSERT_NE(ParseBoolResult::kError, debuggableBool) << Trim(debuggableResult->stdoutStr);
@@ -84,7 +84,7 @@
         }
 
         auto lsResult = execute(Split("adb shell which servicedispatcher", " "), nullptr);
-        ASSERT_THAT(lsResult, Ok());
+        ASSERT_TRUE(lsResult.has_value());
         if (lsResult->exitCode != 0) {
             GTEST_SKIP() << "b/182914638: until feature is fully enabled, skip test on devices "
                             "without servicedispatcher";
@@ -95,7 +95,7 @@
 
         auto service = execute({"adb", "shell", kServiceBinary, kServiceName, kDescriptor},
                                &CommandResult::stdoutEndsWithNewLine);
-        ASSERT_THAT(service, Ok());
+        ASSERT_TRUE(service.has_value());
         ASSERT_EQ(std::nullopt, service->exitCode) << *service;
         mService = std::move(*service);
     }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 341e9ce..cb1a1ee 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -29,17 +29,17 @@
 
 #include <android-base/properties.h>
 #include <android-base/result-gmock.h>
-#include <android-base/result.h>
-#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
+#include <binder/Functional.h>
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #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>
 #include <sys/epoll.h>
@@ -52,10 +52,12 @@
 #define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
 
 using namespace android;
+using namespace android::binder::impl;
 using namespace std::string_literals;
 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;
@@ -846,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));
@@ -1176,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);
@@ -1325,7 +1327,7 @@
     ASSERT_EQ(0, ret);
 
     // Restore the original file limits when the test finishes
-    base::ScopeGuard guardUnguard([&]() { setrlimit(RLIMIT_NOFILE, &origNofile); });
+    auto guardUnguard = make_scope_guard([&]() { setrlimit(RLIMIT_NOFILE, &origNofile); });
 
     rlimit testNofile = {1024, 1024};
     ret = setrlimit(RLIMIT_NOFILE, &testNofile);
@@ -1485,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 {};
@@ -1552,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)));
 }
 
@@ -1822,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) {
@@ -1887,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 1340ea1..2769a88 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -18,7 +18,6 @@
 // only used on NDK tests outside of vendor
 #include <aidl/IBinderRpcTest.h>
 #endif
-#include <android-base/stringprintf.h>
 
 #include <chrono>
 #include <cstdlib>
@@ -26,6 +25,7 @@
 #include <thread>
 #include <type_traits>
 
+#include <dirent.h>
 #include <dlfcn.h>
 #include <poll.h>
 #include <sys/prctl.h>
@@ -36,11 +36,16 @@
 #include <trusty/tipc.h>
 #endif // BINDER_RPC_TO_TRUSTY_TEST
 
+#include "../Utils.h"
 #include "binderRpcTestCommon.h"
 #include "binderRpcTestFixture.h"
 
 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;
@@ -59,12 +64,12 @@
 
 static std::string WaitStatusToString(int wstatus) {
     if (WIFEXITED(wstatus)) {
-        return base::StringPrintf("exit status %d", WEXITSTATUS(wstatus));
+        return std::format("exit status {}", WEXITSTATUS(wstatus));
     }
     if (WIFSIGNALED(wstatus)) {
-        return base::StringPrintf("term signal %d", WTERMSIG(wstatus));
+        return std::format("term signal {}", WTERMSIG(wstatus));
     }
-    return base::StringPrintf("unexpected state %d", wstatus);
+    return std::format("unexpected state {}", wstatus);
 }
 
 static void debugBacktrace(pid_t pid) {
@@ -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;
-        CHECK(android::base::Pipe(&mReadEnd, &childWriteEnd, 0)) << strerror(errno);
-        CHECK(android::base::Pipe(&childReadEnd, &mWriteEnd, 0)) << strerror(errno);
+    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,12 +146,13 @@
     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)));
-    CHECK(fd.ok());
-    CHECK_EQ(0, TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize())));
+    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");
+    }
     return fd;
 }
 
@@ -202,37 +207,33 @@
     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)));
-    int savedErrno = errno;
-    CHECK(serverFd.ok()) << "Could not create socket " << addr.toString() << ": "
-                         << strerror(savedErrno);
+    if (!serverFd.ok()) {
+        PLOGF("Could not create socket %s", addr.toString().c_str());
+    }
 
     if (0 != TEMP_FAILURE_RETRY(connect(serverFd.get(), addr.addr(), addr.addrSize()))) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Could not connect to socket " << addr.toString() << ": "
-                   << strerror(savedErrno);
+        PLOGF("Could not connect to socket %s", addr.toString().c_str());
     }
     return serverFd;
 }
 
 #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)) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+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) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Failed sendMessageOnSocket: " << strerror(savedErrno);
+        PLOGF("Failed sendMessageOnSocket");
     }
     return std::move(sockClient);
 }
@@ -246,10 +247,12 @@
 // threads.
 std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
         const BinderRpcOptions& options) {
-    CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
+    LOG_ALWAYS_FATAL_IF(options.numSessions < 1, "Must have at least one session to a server");
 
     if (options.numIncomingConnectionsBySession.size() != 0) {
-        CHECK_EQ(options.numIncomingConnectionsBySession.size(), options.numSessions);
+        LOG_ALWAYS_FATAL_IF(options.numIncomingConnectionsBySession.size() != options.numSessions,
+                            "%s: %zu != %zu", __func__,
+                            options.numIncomingConnectionsBySession.size(), options.numSessions);
     }
 
     SocketType socketType = GetParam().type;
@@ -259,12 +262,12 @@
     bool singleThreaded = GetParam().singleThreaded;
     bool noKernel = GetParam().noKernel;
 
-    std::string path = android::base::GetExecutableDirectory();
-    auto servicePath = android::base::StringPrintf("%s/binder_rpc_test_service%s%s", path.c_str(),
-                                                   singleThreaded ? "_single_threaded" : "",
-                                                   noKernel ? "_no_kernel" : "");
+    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,14 +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)) {
-            int savedErrno = errno;
-            LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+        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;
@@ -288,8 +290,10 @@
 
                 auto writeFd = std::to_string(writeEnd.get());
                 auto readFd = std::to_string(readEnd.get());
-                execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(), readFd.c_str(),
-                      NULL);
+                auto status = execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(),
+                                    readFd.c_str(), NULL);
+                PLOGF("execl('%s', _, %s, %s) should not return at all, but it returned %d",
+                      servicePath.c_str(), writeFd.c_str(), readFd.c_str(), status);
             }));
 
     BinderRpcTestServerConfig serverConfig;
@@ -334,16 +338,16 @@
         }
         writeToFd(ret->host.writeEnd(), clientInfo);
 
-        CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
+        LOG_ALWAYS_FATAL_IF(serverInfo.port > std::numeric_limits<unsigned int>::max());
         if (socketType == SocketType::INET) {
-            CHECK_NE(0, serverInfo.port);
+            LOG_ALWAYS_FATAL_IF(0 == serverInfo.port);
         }
 
         if (rpcSecurity == RpcSecurity::TLS) {
             const auto& serverCert = serverInfo.cert.data;
-            CHECK_EQ(OK,
-                     certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
-                                                             serverCert));
+            LOG_ALWAYS_FATAL_IF(
+                    OK !=
+                    certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM, serverCert));
         }
     }
 
@@ -356,7 +360,7 @@
                 ? options.numIncomingConnectionsBySession.at(i)
                 : 0;
 
-        CHECK(session->setProtocolVersion(clientVersion));
+        LOG_ALWAYS_FATAL_IF(!session->setProtocolVersion(clientVersion));
         session->setMaxIncomingThreads(numIncoming);
         session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
@@ -373,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);
@@ -390,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;
@@ -408,7 +412,7 @@
             ret->sessions.clear();
             break;
         }
-        CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
+        LOG_ALWAYS_FATAL_IF(status != OK, "Could not connect: %s", statusToString(status).c_str());
         ret->sessions.push_back({session, session->getRootObject()});
     }
     return ret;
@@ -589,12 +593,12 @@
     android::os::ParcelFileDescriptor fdA;
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdA));
     std::string result;
-    CHECK(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));
-    CHECK(android::base::ReadFdToString(fdB.get(), &result));
+    ASSERT_TRUE(ReadFdToString(fdB.get(), &result));
     EXPECT_EQ(result, "b");
 
     saturateThreadPool(kNumServerThreads, proc.rootIface);
@@ -951,8 +955,8 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
-    EXPECT_EQ(result, "hello");
+    ASSERT_TRUE(ReadFdToString(out.get(), &result));
+    ASSERT_EQ(result, "hello");
 }
 
 TEST_P(BinderRpc, SendFiles) {
@@ -981,7 +985,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, "123abcd");
 }
 
@@ -1006,7 +1010,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, std::string(253, 'a'));
 }
 
@@ -1151,14 +1155,14 @@
     // 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) {
         return false;
     }
 
-    LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
+    LOG_ALWAYS_FATAL_IF(!serverFd.ok(), "Could not create socket: %s", strerror(errno));
 
     sockaddr_vm serverAddr{
             .svm_family = AF_VSOCK,
@@ -1178,9 +1182,9 @@
     // 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 == -1, "Could not create socket for port %u: %s", vsockPort,
+    LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
                         strerror(errno));
 
     bool success = false;
@@ -1192,13 +1196,13 @@
     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},
                     {.fd = connectFd.get(), .events = POLLOUT, .revents = 0},
             };
-            ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
+            ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
             LOG_ALWAYS_FATAL_IF(ret < 0, "Error polling: %s", strerror(errno));
 
             if (pfd[0].revents & POLLIN) {
@@ -1359,7 +1363,11 @@
 };
 
 TEST(BinderRpc, Java) {
-#if !defined(__ANDROID__)
+    bool expectDebuggable = false;
+#if defined(__ANDROID__)
+    expectDebuggable = android::base::GetBoolProperty("ro.debuggable", false) &&
+            android::base::GetProperty("ro.build.type", "") != "user";
+#else
     GTEST_SKIP() << "This test is only run on Android. Though it can technically run on host on"
                     "createRpcDelegateServiceManager() with a device attached, such test belongs "
                     "to binderHostDeviceTest. Hence, just disable this test on host.";
@@ -1387,8 +1395,7 @@
     auto keepAlive = sp<BBinder>::make();
     auto setRpcClientDebugStatus = binder->setRpcClientDebug(std::move(socket), keepAlive);
 
-    if (!android::base::GetBoolProperty("ro.debuggable", false) ||
-        android::base::GetProperty("ro.build.type", "") == "user") {
+    if (!expectDebuggable) {
         ASSERT_EQ(INVALID_OPERATION, setRpcClientDebugStatus)
                 << "setRpcClientDebug should return INVALID_OPERATION on non-debuggable or user "
                    "builds, but get "
@@ -1419,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());
 }
@@ -1472,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;
@@ -1506,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(
@@ -1550,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));
@@ -1558,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: {
@@ -1582,24 +1589,22 @@
             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)};
 
             if (binder::os::receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
-                int savedErrno = errno;
-                LOG(FATAL) << "Failed receiveMessage: " << strerror(savedErrno);
+                PLOGF("Failed receiveMessage");
             }
-            if (fds.size() != 1) {
-                LOG(FATAL) << "Expected one FD from receiveMessage(), got " << fds.size();
-            }
-            return std::move(std::get<base::unique_fd>(fds[0]));
+            LOG_ALWAYS_FATAL_IF(fds.size() != 1, "Expected one FD from receiveMessage(), got %zu",
+                                fds.size());
+            return std::move(std::get<unique_fd>(fds[0]));
         }
 
         void run() {
@@ -1607,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());
@@ -2083,7 +2088,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
-    android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+    __android_log_set_logger(__android_log_stderr_logger);
 
     return RUN_ALL_TESTS();
 }
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index eceff35..62fe9e5 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -22,7 +22,6 @@
 #include <BnBinderRpcCallback.h>
 #include <BnBinderRpcSession.h>
 #include <BnBinderRpcTest.h>
-#include <android-base/stringprintf.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
 #include <binder/IPCThreadState.h>
@@ -37,10 +36,11 @@
 #include <string>
 #include <vector>
 
-#ifndef __TRUSTY__
-#include <android-base/file.h>
-#include <android-base/logging.h>
+#ifdef __ANDROID__
 #include <android-base/properties.h>
+#endif
+
+#ifndef __TRUSTY__
 #include <android/binder_auto_utils.h>
 #include <android/binder_libbinder.h>
 #include <binder/ProcessState.h>
@@ -57,7 +57,10 @@
 
 #include "../BuildFlags.h"
 #include "../FdTrigger.h"
+#include "../FdUtils.h"
 #include "../RpcState.h" // for debugging
+#include "FileUtils.h"
+#include "format.h"
 #include "utils/Errors.h"
 
 namespace android {
@@ -74,8 +77,7 @@
 #ifdef __ANDROID__
     return base::GetProperty("ro.build.version.codename", "") != "REL";
 #else
-    // TODO(b/305983144): restrict on other platforms
-    return true;
+    return false;
 #endif
 }
 
@@ -91,7 +93,7 @@
 }
 
 static inline std::string trustyIpcPort(uint32_t serverVersion) {
-    return base::StringPrintf("com.android.trusty.binderRpcTestService.V%" PRIu32, serverVersion);
+    return std::format("com.android.trusty.binderRpcTestService.V{}", serverVersion);
 }
 
 enum class SocketType {
@@ -155,33 +157,34 @@
 };
 
 #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();
-    CHECK(android::base::WriteFully(fd, &length, sizeof(length)));
-    CHECK(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;
-    CHECK(android::base::ReadFully(fd, &length, sizeof(length)));
+    LOG_ALWAYS_FATAL_IF(!android::binder::ReadFully(fd, &length, sizeof(length)));
     std::string ret(length, '\0');
-    CHECK(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;
-    CHECK_EQ(OK, parcelable.writeToParcel(&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;
-    CHECK_EQ(OK, parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
+    LOG_ALWAYS_FATAL_IF(OK !=
+                        parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
     T object;
-    CHECK_EQ(OK, object.readFromParcel(&parcel));
+    LOG_ALWAYS_FATAL_IF(OK != object.readFromParcel(&parcel));
     return object;
 }
 
@@ -206,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;
-    CHECK(android::base::Pipe(&readFd, &writeFd)) << 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));
@@ -390,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 7435f30..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;
-            CHECK(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();
     }
 
@@ -98,11 +100,11 @@
 };
 
 int main(int argc, char* argv[]) {
-    android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+    __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);
@@ -118,33 +120,36 @@
     auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
     sp<RpcServer> server = RpcServer::make(newTlsFactory(rpcSecurity, certVerifier));
 
-    CHECK(server->setProtocolVersion(serverConfig.serverVersion));
+    LOG_ALWAYS_FATAL_IF(!server->setProtocolVersion(serverConfig.serverVersion));
     server->setMaxThreads(serverConfig.numThreads);
     server->setSupportedFileDescriptorTransportModes(serverSupportedFileDescriptorTransportModes);
 
     unsigned int outPort = 0;
-    base::unique_fd socketFd(serverConfig.socketFd);
+    unique_fd socketFd(serverConfig.socketFd);
 
     switch (socketType) {
         case SocketType::PRECONNECTED:
             [[fallthrough]];
         case SocketType::UNIX:
-            CHECK_EQ(OK, server->setupUnixDomainServer(serverConfig.addr.c_str()))
-                    << serverConfig.addr;
+            LOG_ALWAYS_FATAL_IF(OK != server->setupUnixDomainServer(serverConfig.addr.c_str()),
+                                "%s", serverConfig.addr.c_str());
             break;
         case SocketType::UNIX_BOOTSTRAP:
-            CHECK_EQ(OK, server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
             break;
         case SocketType::UNIX_RAW:
-            CHECK_EQ(OK, server->setupRawSocketServer(std::move(socketFd)));
+            LOG_ALWAYS_FATAL_IF(OK != server->setupRawSocketServer(std::move(socketFd)));
             break;
         case SocketType::VSOCK:
-            CHECK_EQ(OK, server->setupVsockServer(VMADDR_CID_LOCAL, serverConfig.vsockPort))
-                    << "Need `sudo modprobe vsock_loopback`?";
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                        server->setupVsockServer(VMADDR_CID_LOCAL,
+                                                                 serverConfig.vsockPort),
+                                "Need `sudo modprobe vsock_loopback`?");
             break;
         case SocketType::INET: {
-            CHECK_EQ(OK, server->setupInetServer(kLocalInetAddress, 0, &outPort));
-            CHECK_NE(0, outPort);
+            LOG_ALWAYS_FATAL_IF(OK != server->setupInetServer(kLocalInetAddress, 0, &outPort));
+            LOG_ALWAYS_FATAL_IF(0 == outPort);
             break;
         }
         default:
@@ -159,21 +164,21 @@
 
     if (rpcSecurity == RpcSecurity::TLS) {
         for (const auto& clientCert : clientInfo.certs) {
-            CHECK_EQ(OK,
-                     certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
-                                                             clientCert.data));
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
+                                                                        clientCert.data));
         }
     }
 
     server->setPerSessionRootObject([&](wp<RpcSession> session, const void* addrPtr, size_t len) {
         {
             sp<RpcSession> spSession = session.promote();
-            CHECK_NE(nullptr, spSession.get());
+            LOG_ALWAYS_FATAL_IF(nullptr == spSession.get());
         }
 
         // UNIX sockets with abstract addresses return
         // sizeof(sa_family_t)==2 in addrlen
-        CHECK_GE(len, sizeof(sa_family_t));
+        LOG_ALWAYS_FATAL_IF(len < sizeof(sa_family_t));
         const sockaddr* addr = reinterpret_cast<const sockaddr*>(addrPtr);
         sp<MyBinderRpcTestAndroid> service = sp<MyBinderRpcTestAndroid>::make();
         switch (addr->sa_family) {
@@ -181,15 +186,15 @@
                 // nothing to save
                 break;
             case AF_VSOCK:
-                CHECK_EQ(len, sizeof(sockaddr_vm));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_vm));
                 service->port = reinterpret_cast<const sockaddr_vm*>(addr)->svm_port;
                 break;
             case AF_INET:
-                CHECK_EQ(len, sizeof(sockaddr_in));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
                 service->port = ntohs(reinterpret_cast<const sockaddr_in*>(addr)->sin_port);
                 break;
             case AF_INET6:
-                CHECK_EQ(len, sizeof(sockaddr_in));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
                 service->port = ntohs(reinterpret_cast<const sockaddr_in6*>(addr)->sin6_port);
                 break;
             default:
diff --git a/libs/binder/tests/binderRpcTestServiceTrusty.cpp b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
index cb632e9..8346b36 100644
--- a/libs/binder/tests/binderRpcTestServiceTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
@@ -16,7 +16,6 @@
 
 #define TLOG_TAG "binderRpcTestService"
 
-#include <android-base/stringprintf.h>
 #include <binder/RpcServerTrusty.h>
 #include <inttypes.h>
 #include <lib/tipc/tipc.h>
@@ -28,7 +27,6 @@
 #include "binderRpcTestCommon.h"
 
 using namespace android;
-using android::base::StringPrintf;
 using binder::Status;
 
 static int gConnectionCounter = 0;
@@ -79,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 fcb83bd..18751cc 100644
--- a/libs/binder/tests/binderRpcTestTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -16,13 +16,14 @@
 
 #define LOG_TAG "binderRpcTest"
 
-#include <android-base/stringprintf.h>
 #include <binder/RpcTransportTipcTrusty.h>
 #include <trusty-gtest.h>
 #include <trusty_ipc.h>
 
 #include "binderRpcTestFixture.h"
 
+using android::binder::unique_fd;
+
 namespace android {
 
 // Destructors need to be defined, even if pure virtual
@@ -75,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/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp
index 7ec7c99..e59dc82 100644
--- a/libs/binder/tests/binderRpcWireProtocolTest.cpp
+++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <android-base/logging.h>
-#include <android-base/macros.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <binder/Parcel.h>
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 1c13866..41cb552 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -28,6 +28,7 @@
 #include <gtest/gtest.h>
 #pragma clang diagnostic pop
 
+#include <utils/Flattenable.h>
 #include <utils/LightRefBase.h>
 #include <utils/NativeHandle.h>
 
@@ -40,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 {
@@ -684,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/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 25e286c..6301c74 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -32,7 +32,7 @@
 
 TEST(UtilsHost, ExecuteImmediately) {
     auto result = execute({"echo", "foo"}, nullptr);
-    ASSERT_THAT(result, Ok());
+    ASSERT_TRUE(result.has_value());
     EXPECT_THAT(result->exitCode, Optional(EX_OK));
     EXPECT_EQ(result->stdoutStr, "foo\n");
 }
@@ -58,7 +58,7 @@
         EXPECT_GE(elapsedMs, 1000);
         EXPECT_LT(elapsedMs, 2000);
 
-        ASSERT_THAT(result, Ok());
+        ASSERT_TRUE(result.has_value());
         EXPECT_EQ(std::nullopt, result->exitCode);
         EXPECT_EQ(result->stdoutStr, "foo\n");
     }
@@ -83,7 +83,7 @@
         EXPECT_GE(elapsedMs, 4000);
         EXPECT_LT(elapsedMs, 6000);
 
-        ASSERT_THAT(result, Ok());
+        ASSERT_TRUE(result.has_value());
         EXPECT_EQ(std::nullopt, result->exitCode);
         EXPECT_EQ(result->stdoutStr, "foo\n");
     }
@@ -104,7 +104,7 @@
         return false;
     });
 
-    ASSERT_THAT(result, Ok());
+    ASSERT_TRUE(result.has_value());
     EXPECT_EQ(std::nullopt, result->exitCode);
     EXPECT_THAT(result->signal, Optional(SIGKILL));
 }
diff --git a/libs/binder/tests/format.h b/libs/binder/tests/format.h
new file mode 100644
index 0000000..b5440a4
--- /dev/null
+++ b/libs/binder/tests/format.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+// TODO(b/302723053): remove this header and replace with <format> once b/175635923 is done
+// ETA for this blocker is 2023-10-27~2023-11-10.
+// Also, remember to remove fmtlib's format.cc from trusty makefiles.
+
+#if __has_include(<format>)
+#include <format>
+#else
+#include <fmt/format.h>
+
+namespace std {
+using fmt::format;
+}
+#endif
\ No newline at end of file
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 383795e..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,
@@ -32,7 +35,11 @@
     host_supported: true,
 
     fuzz_config: {
-        cc: ["smoreland@google.com"],
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        use_for_presubmit: true,
     },
 
     srcs: [
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 416ffad..08fe071 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -25,11 +25,13 @@
 #include <binder/ParcelableHolder.h>
 #include <binder/PersistableBundle.h>
 #include <binder/Status.h>
+#include <utils/Flattenable.h>
 
 #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 {};
@@ -306,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 e494366..87b0fb6 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
@@ -14,19 +14,20 @@
  * limitations under the License.
  */
 
-#include <android-base/macros.h>
 #include <binder/RecordedTransaction.h>
 #include <filesystem>
 
 #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);
 
@@ -35,8 +36,8 @@
     if (transaction.has_value()) {
         intermediateFile = std::tmpfile();
 
-        android::base::unique_fd fdForWriting(fileno(intermediateFile));
-        auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting);
+        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 33a653e..fa939e6 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/macros.h>
 #include <binder/RecordedTransaction.h>
 #include <fuzzbinder/random_parcel.h>
 #include <filesystem>
@@ -23,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);
@@ -54,8 +54,8 @@
 
     if (transaction.has_value()) {
         std::FILE* intermediateFile = std::tmpfile();
-        android::base::unique_fd fdForWriting(dup(fileno(intermediateFile)));
-        auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting);
+        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 43e06e0..a8dabc3 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -22,17 +22,34 @@
 #endif
 
 #include <binder/RpcTransportTipcTrusty.h>
+#include <log/log.h>
+#include <trusty_log.h>
 
 #include "../OS.h"
 #include "TrustyStatus.h"
 
-using android::base::Result;
+#include <cstdarg>
+
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
 
 namespace android::binder::os {
 
-Result<void> 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 {};
+    return OK;
 }
 
 status_t getRandomBytes(uint8_t* data, size_t size) {
@@ -61,16 +78,51 @@
 
 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;
+}
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 692f82d..c74ba0a 100644
--- a/libs/binder/trusty/RpcTransportTipcTrusty.cpp
+++ b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
@@ -29,6 +29,10 @@
 
 namespace android {
 
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
+
 // RpcTransport for Trusty.
 class RpcTransportTipcTrusty : public RpcTransport {
 public:
@@ -45,9 +49,8 @@
 
     status_t interruptableWriteFully(
             FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& /*altPoll*/,
-            const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
-            override {
+            const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
+            const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         if (niovs < 0) {
             return BAD_VALUE;
         }
@@ -115,8 +118,8 @@
 
     status_t interruptableReadFully(
             FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
-            const std::optional<android::base::function_ref<status_t()>>& /*altPoll*/,
-            std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+            const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
+            std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
         if (niovs < 0) {
             return BAD_VALUE;
         }
@@ -168,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/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
index 1cefac5..6e20b8a 100644
--- a/libs/binder/trusty/binderRpcTest/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -2,5 +2,5 @@
     "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
     "app_name": "binderRpcTest",
     "min_heap": 262144,
-    "min_stack": 16384
+    "min_stack": 20480
 }
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
index 975f689..e46ccfb 100644
--- a/libs/binder/trusty/binderRpcTest/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -21,6 +21,7 @@
 MANIFEST := $(LOCAL_DIR)/manifest.json
 
 MODULE_SRCS += \
+	$(FMTLIB_DIR)/src/format.cc \
 	$(LIBBINDER_TESTS_DIR)/binderRpcUniversalTests.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestTrusty.cpp \
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/binderRpcTest/service/rules.mk b/libs/binder/trusty/binderRpcTest/service/rules.mk
index 5d1a51d..50ae3d2 100644
--- a/libs/binder/trusty/binderRpcTest/service/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/service/rules.mk
@@ -21,6 +21,7 @@
 MANIFEST := $(LOCAL_DIR)/manifest.json
 
 MODULE_SRCS := \
+	$(FMTLIB_DIR)/src/format.cc \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
 	$(LIBBINDER_TESTS_DIR)/binderRpcTestServiceTrusty.cpp \
 
diff --git a/libs/binder/trusty/binder_rpc_unstable/rules.mk b/libs/binder/trusty/binder_rpc_unstable/rules.mk
new file mode 100644
index 0000000..d8dbce5
--- /dev/null
+++ b/libs/binder/trusty/binder_rpc_unstable/rules.mk
@@ -0,0 +1,32 @@
+# 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)/libbinder_rpc_unstable.cpp \
+
+MODULE_EXPORT_INCLUDES += \
+	$(LIBBINDER_DIR)/include_rpc_unstable \
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index 8924b36..f35d6c2 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,13 +16,11 @@
 
 #pragma once
 
-#include <android-base/expected.h>
-#include <android-base/macros.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>
 
@@ -54,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);
@@ -83,7 +81,8 @@
     // Both this class and RpcServer have multiple non-copyable fields,
     // including mPortAcl below which can't be copied because mUuidPtrs
     // holds pointers into it
-    DISALLOW_COPY_AND_ASSIGN(RpcServerTrusty);
+    RpcServerTrusty(const RpcServerTrusty&) = delete;
+    void operator=(const RpcServerTrusty&) = delete;
 
     friend sp<RpcServerTrusty>;
     explicit RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
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 1f05ef7..5cbe0af 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -18,35 +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)/../logging.cpp \
+	$(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 \
@@ -57,17 +54,22 @@
 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_VENDOR__ \
 	-D__ANDROID_VNDK__ \
 
 MODULE_DEPS += \
diff --git a/libs/binder/trusty/logging.cpp b/libs/binder/trusty/logging.cpp
deleted file mode 100644
index b4243af..0000000
--- a/libs/binder/trusty/logging.cpp
+++ /dev/null
@@ -1,166 +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.
- */
-
-#define TLOG_TAG "libbinder"
-
-#include "android-base/logging.h"
-
-#include <trusty_log.h>
-#include <iostream>
-#include <string>
-
-#include <android-base/macros.h>
-#include <android-base/strings.h>
-
-namespace android {
-namespace base {
-
-static const char* GetFileBasename(const char* file) {
-    const char* last_slash = strrchr(file, '/');
-    if (last_slash != nullptr) {
-        return last_slash + 1;
-    }
-    return file;
-}
-
-// This splits the message up line by line, by calling log_function with a pointer to the start of
-// each line and the size up to the newline character.  It sends size = -1 for the final line.
-template <typename F, typename... Args>
-static void SplitByLines(const char* msg, const F& log_function, Args&&... args) {
-    const char* newline;
-    while ((newline = strchr(msg, '\n')) != nullptr) {
-        log_function(msg, newline - msg, args...);
-        msg = newline + 1;
-    }
-
-    log_function(msg, -1, args...);
-}
-
-void DefaultAborter(const char* abort_message) {
-    TLOGC("aborting: %s\n", abort_message);
-    abort();
-}
-
-static void TrustyLogLine(const char* msg, int /*length*/, android::base::LogSeverity severity,
-                          const char* tag) {
-    switch (severity) {
-        case VERBOSE:
-        case DEBUG:
-            TLOGD("%s: %s\n", tag, msg);
-            break;
-        case INFO:
-            TLOGI("%s: %s\n", tag, msg);
-            break;
-        case WARNING:
-            TLOGW("%s: %s\n", tag, msg);
-            break;
-        case ERROR:
-            TLOGE("%s: %s\n", tag, msg);
-            break;
-        case FATAL_WITHOUT_ABORT:
-        case FATAL:
-            TLOGC("%s: %s\n", tag, msg);
-            break;
-    }
-}
-
-void TrustyLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag,
-                  const char*, unsigned int, const char* full_message) {
-    SplitByLines(full_message, TrustyLogLine, severity, tag);
-}
-
-// This indirection greatly reduces the stack impact of having lots of
-// checks/logging in a function.
-class LogMessageData {
-public:
-    LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                   int error)
-          : file_(GetFileBasename(file)),
-            line_number_(line),
-            severity_(severity),
-            tag_(tag),
-            error_(error) {}
-
-    const char* GetFile() const { return file_; }
-
-    unsigned int GetLineNumber() const { return line_number_; }
-
-    LogSeverity GetSeverity() const { return severity_; }
-
-    const char* GetTag() const { return tag_; }
-
-    int GetError() const { return error_; }
-
-    std::ostream& GetBuffer() { return buffer_; }
-
-    std::string ToString() const { return buffer_.str(); }
-
-private:
-    std::ostringstream buffer_;
-    const char* const file_;
-    const unsigned int line_number_;
-    const LogSeverity severity_;
-    const char* const tag_;
-    const int error_;
-
-    DISALLOW_COPY_AND_ASSIGN(LogMessageData);
-};
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
-                       const char* tag, int error)
-      : LogMessage(file, line, severity, tag, error) {}
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                       int error)
-      : data_(new LogMessageData(file, line, severity, tag, error)) {}
-
-LogMessage::~LogMessage() {
-    // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
-    if (!WOULD_LOG(data_->GetSeverity())) {
-        return;
-    }
-
-    // Finish constructing the message.
-    if (data_->GetError() != -1) {
-        data_->GetBuffer() << ": " << strerror(data_->GetError());
-    }
-    std::string msg(data_->ToString());
-
-    LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(),
-            msg.c_str());
-
-    // Abort if necessary.
-    if (data_->GetSeverity() == FATAL) {
-        DefaultAborter(msg.c_str());
-    }
-}
-
-std::ostream& LogMessage::stream() {
-    return data_->GetBuffer();
-}
-
-void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                         const char* message) {
-    TrustyLogger(DEFAULT, severity, tag ?: "<unknown>", file, line, message);
-}
-
-bool ShouldLog(LogSeverity /*severity*/, const char* /*tag*/) {
-    // This is controlled by Trusty's log level.
-    return true;
-}
-
-} // namespace base
-} // namespace android
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 2e56cbd..f2f140d 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -18,13 +18,13 @@
 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)/logging.cpp \
 	$(LOCAL_DIR)/OS.cpp \
 	$(LOCAL_DIR)/RpcServerTrusty.cpp \
 	$(LOCAL_DIR)/RpcTransportTipcTrusty.cpp \
@@ -35,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 \
@@ -44,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
@@ -71,6 +68,11 @@
 
 MODULE_EXPORT_COMPILEFLAGS += \
 	-DBINDER_RPC_SINGLE_THREADED \
+	-DBINDER_ENABLE_LIBLOG_ASSERT \
+	-DBINDER_DISABLE_NATIVE_HANDLE \
+	-DBINDER_DISABLE_BLOB \
+	-DBINDER_NO_LIBBASE \
+	-D__ANDROID_VENDOR__ \
 	-D__ANDROID_VNDK__ \
 
 # libbinder has some deprecated declarations that we want to produce warnings
diff --git a/libs/gui/include/gui/Flags.h b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp
similarity index 71%
copy from libs/gui/include/gui/Flags.h
copy to libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp
index a2cff56..6f96566 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -14,9 +14,4 @@
  * limitations under the License.
  */
 
-#pragma once
-
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+#include <binder_rpc_unstable.hpp>
diff --git a/libs/gui/include/gui/Flags.h b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs
similarity index 71%
copy from libs/gui/include/gui/Flags.h
copy to libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs
index a2cff56..c7036f4 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-#pragma once
+//! Generated Rust bindings to binder_rpc_unstable
 
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+#[allow(bad_style)]
+mod sys {
+    include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk
new file mode 100644
index 0000000..ef1b7c3
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk
@@ -0,0 +1,40 @@
+# 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 := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_unstable_bindgen
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/binder_rpc_unstable \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+	trusty/user/base/lib/libstdc++-trusty \
+	trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := $(LOCAL_DIR)/BinderBindings.hpp
+
+MODULE_BINDGEN_FLAGS += \
+	--blocklist-type="AIBinder" \
+	--raw-line="use binder_ndk_sys::AIBinder;" \
+	--rustified-enum="ARpcSession_FileDescriptorTransportMode" \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
new file mode 100644
index 0000000..76f3b94
--- /dev/null
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -0,0 +1,35 @@
+# 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/rpcbinder/src/lib.rs
+
+MODULE_CRATE_NAME := rpcbinder
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	$(LIBBINDER_DIR)/trusty/rust \
+	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+	external/rust/crates/foreign-types \
+	trusty/user/base/lib/tipc/rust \
+	trusty/user/base/lib/trusty-sys \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
new file mode 100644
index 0000000..d343f14
--- /dev/null
+++ b/libs/binder/trusty/rust/rules.mk
@@ -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.
+#
+
+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 \
+	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+	external/rust/crates/downcast-rs \
+	trusty/user/base/lib/trusty-sys \
+
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
+# 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/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index d4605ea..3fe71ce 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -34,5 +34,7 @@
         "libbase",
         "liblog",
     ],
+    static_libs: ["libguiflags"],
     export_include_dirs: ["include"],
+    export_static_lib_headers: ["libguiflags"],
 }
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
index 0ecf94c..bb573c5 100644
--- a/libs/bufferstreams/examples/app/Android.bp
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -14,14 +14,36 @@
 
 android_app {
     name: "BufferStreamsDemoApp",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/**/*.kt"],
     sdk_version: "current",
 
     jni_uses_platform_apis: true,
     jni_libs: ["libbufferstreamdemoapp"],
     use_embedded_native_libs: true,
+    kotlincflags: [
+        "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
+    ],
+    optimize: {
+        proguard_flags_files: ["proguard-rules.pro"],
+    },
+
+    resource_dirs: ["res"],
 
     static_libs: [
+        "androidx.activity_activity-compose",
         "androidx.appcompat_appcompat",
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.material3_material3",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui",
+        "androidx.compose.ui_ui-graphics",
+        "androidx.compose.ui_ui-tooling-preview",
+        "androidx.core_core-ktx",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.navigation_navigation-common-ktx",
+        "androidx.navigation_navigation-compose",
+        "androidx.navigation_navigation-fragment-ktx",
+        "androidx.navigation_navigation-runtime-ktx",
+        "androidx.navigation_navigation-ui-ktx",
     ],
 }
diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml
index 872193c..a5e2fa8 100644
--- a/libs/bufferstreams/examples/app/AndroidManifest.xml
+++ b/libs/bufferstreams/examples/app/AndroidManifest.xml
@@ -9,14 +9,15 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:theme="@style/Theme.AppCompat.Light"
+        android:theme="@style/Theme.Jetpack"
         tools:targetApi="34">
         <activity
             android:name=".MainActivity"
-            android:exported="true">
+            android:exported="true"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.Jetpack">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
new file mode 100644
index 0000000..ff3ae5a
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
@@ -0,0 +1,40 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+
+@Composable
+fun BufferDemosAppBar(
+        currentScreen: BufferDemoScreen,
+        canNavigateBack: Boolean,
+        navigateUp: () -> Unit,
+        modifier: Modifier = Modifier
+) {
+    TopAppBar(
+            title = { Text(stringResource(currentScreen.title)) },
+            colors =
+            TopAppBarDefaults.mediumTopAppBarColors(
+                    containerColor = MaterialTheme.colorScheme.primaryContainer
+            ),
+            modifier = modifier,
+            navigationIcon = {
+                if (canNavigateBack) {
+                    IconButton(onClick = navigateUp) {
+                        Icon(
+                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+                                contentDescription = stringResource(R.string.back_button)
+                        )
+                    }
+                }
+            }
+    )
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ede7793
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -0,0 +1,27 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+class BufferStreamJNI {
+    // Used to load the 'bufferstreamsdemoapp' library on application startup.
+    init {
+        System.loadLibrary("bufferstreamdemoapp")
+    }
+
+    /**
+     * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
+     * packaged with this application.
+     */
+    external fun stringFromJNI(): String;
+    external fun testBufferQueueCreation();
+
+    companion object {
+        fun companion_stringFromJNI(): String {
+            val instance = BufferStreamJNI()
+            return instance.stringFromJNI()
+        }
+
+        fun companion_testBufferQueueCreation() {
+            val instance = BufferStreamJNI()
+            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
new file mode 100644
index 0000000..95e415e
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -0,0 +1,35 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun DemoScreen1(modifier: Modifier = Modifier) {
+    Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
+        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 = { LogOutput.getInstance().clearText() }) {
+                        Text("Clear")
+                    }
+            }
+        }
+    }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
new file mode 100644
index 0000000..5efee92
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen2() {
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
new file mode 100644
index 0000000..8cba857
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen3() {
+}
\ 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.java b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
deleted file mode 100644
index 67b95a5..0000000
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.android.graphics.bufferstreamsdemoapp;
-
-import android.os.Bundle;
-import android.widget.TextView;
-import androidx.appcompat.app.AppCompatActivity;
-
-public class MainActivity extends AppCompatActivity {
-    // Used to load the 'bufferstreamsdemoapp' library on application startup.
-    static { System.loadLibrary("bufferstreamdemoapp"); }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        RunBufferQueue();
-        System.out.println("stringFromJNI: " + stringFromJNI());
-    }
-
-    /**
-     * A native method that is implemented by the 'bufferstreamsdemoapp' native
-     * library, which is packaged with this application.
-     */
-    public native String stringFromJNI();
-    public native void RunBufferQueue();
-}
\ No newline at end of file
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
new file mode 100644
index 0000000..2ccd8d7
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -0,0 +1,132 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+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()
+                    }
+            }
+        }
+    }
+}
+
+enum class BufferDemoScreen(val route: String, @StringRes val title: Int) {
+    Start(route = "start", title = R.string.start),
+    Demo1(route = "demo1", title = R.string.demo1),
+    Demo2(route = "demo2", title = R.string.demo2),
+    Demo3(route = "demo3", title = R.string.demo3);
+
+    companion object {
+        fun findByRoute(route: String): BufferDemoScreen {
+            return values().find { it.route == route }!!
+        }
+    }
+}
+
+@Composable
+fun BufferDemosApp() {
+    var navController: NavHostController = rememberNavController()
+    // Get current back stack entry
+    val backStackEntry by navController.currentBackStackEntryAsState()
+    // Get the name of the current screen
+    val currentScreen =
+        BufferDemoScreen.findByRoute(
+            backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
+
+    Scaffold(
+        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() }
+                }
+        }
+}
+
+@Composable
+fun DemoList(onButtonClicked: (String) -> Unit) {
+    var modifier = Modifier.fillMaxSize().padding(16.dp)
+
+    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))
+            }
+        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) })
+                    }
+                }
+        }
+    }
+}
+
+@Composable
+fun SelectDemoButton(name: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
+    Button(onClick = onClick, modifier = modifier.widthIn(min = 250.dp)) { Text(name) }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
new file mode 100644
index 0000000..d85ea72
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
@@ -0,0 +1,11 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
new file mode 100644
index 0000000..fccd93a
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
@@ -0,0 +1,60 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+  primary = Purple80,
+  secondary = PurpleGrey80,
+  tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+  primary = Purple40,
+  secondary = PurpleGrey40,
+  tertiary = Pink40
+)
+
+@Composable
+fun JetpackTheme(
+  darkTheme: Boolean = isSystemInDarkTheme(),
+  // Dynamic color is available on Android 12+
+  dynamicColor: Boolean = true,
+  content: @Composable () -> Unit
+) {
+  val colorScheme = when {
+    dynamicColor -> {
+      val context = LocalContext.current
+      if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+    }
+
+    darkTheme -> DarkColorScheme
+    else -> LightColorScheme
+  }
+  val view = LocalView.current
+  if (!view.isInEditMode) {
+    SideEffect {
+      val window = (view.context as Activity).window
+      window.statusBarColor = colorScheme.primary.toArgb()
+      WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+    }
+  }
+
+  MaterialTheme(
+    colorScheme = colorScheme,
+    typography = Typography,
+    content = content
+  )
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
new file mode 100644
index 0000000..06814ea
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
@@ -0,0 +1,18 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+  bodyLarge = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 16.sp,
+    lineHeight = 24.sp,
+    letterSpacing = 0.5.sp
+  )
+)
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
index 34e0eb4..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_MainActivity_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_MainActivity_RunBufferQueue(
-            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/bufferstreams/examples/app/res/layout/activity_main.xml b/libs/bufferstreams/examples/app/res/layout/activity_main.xml
deleted file mode 100644
index 79fb331..0000000
--- a/libs/bufferstreams/examples/app/res/layout/activity_main.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".MainActivity">
-
-    <TextView
-        android:id="@+id/sample_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Hello World!"
-        tools:layout_editor_absoluteX="100dp"
-        tools:layout_editor_absoluteY="100dp" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml
index e652102..75c8ab5 100644
--- a/libs/bufferstreams/examples/app/res/values/strings.xml
+++ b/libs/bufferstreams/examples/app/res/values/strings.xml
@@ -1,3 +1,8 @@
 <resources>
     <string name="app_name">Buffer Demos</string>
+    <string name="start">Start</string>
+    <string name="demo1">Demo 1</string>
+    <string name="demo2">Demo 2</string>
+    <string name="demo3">Demo 3</string>
+    <string name="back_button">Back</string>
 </resources>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/themes.xml b/libs/bufferstreams/examples/app/res/values/themes.xml
new file mode 100644
index 0000000..eeb308a
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/themes.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="Theme.Jetpack" parent="android:Theme.Material.Light.NoActionBar" />
+</resources>
\ No newline at end of file
diff --git a/libs/fakeservicemanager/FakeServiceManager.cpp b/libs/fakeservicemanager/FakeServiceManager.cpp
index ae242f3..08f30de 100644
--- a/libs/fakeservicemanager/FakeServiceManager.cpp
+++ b/libs/fakeservicemanager/FakeServiceManager.cpp
@@ -122,9 +122,19 @@
 }
 
 void FakeServiceManager::clear() {
-    std::lock_guard<std::mutex> l(mMutex);
+    std::map<String16, sp<IBinder>> backup;
 
-    mNameToService.clear();
+    {
+      std::lock_guard<std::mutex> l(mMutex);
+      backup = mNameToService;
+      mNameToService.clear();
+    }
+
+    // destructors may access FSM, so avoid recursive lock
+    backup.clear(); // explicit
+
+    // TODO: destructors may have added more services here - may want
+    // to check this or abort
 }
 }  // namespace android
 
@@ -147,4 +157,4 @@
     LOG_ALWAYS_FATAL_IF(gFakeServiceManager == nullptr, "Fake Service Manager is not available. Forgot to call setupFakeServiceManager?");
     gFakeServiceManager->clear();
 }
-} //extern "C"
\ No newline at end of file
+} //extern "C"
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index ea1b5e4..918680d 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -17,6 +17,7 @@
         "enum_test.cpp",
         "fake_guard_test.cpp",
         "flags_test.cpp",
+        "function_test.cpp",
         "future_test.cpp",
         "match_test.cpp",
         "mixins_test.cpp",
diff --git a/libs/ftl/function_test.cpp b/libs/ftl/function_test.cpp
new file mode 100644
index 0000000..91b5e08
--- /dev/null
+++ b/libs/ftl/function_test.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright 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 <ftl/function.h>
+#include <gtest/gtest.h>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <type_traits>
+
+namespace android::test {
+namespace {
+
+// Create an alias to composite requirements defined by the trait class `T` for easier testing.
+template <typename T, typename S>
+inline constexpr bool is_opaquely_storable = (T::template require_trivially_copyable<S> &&
+                                              T::template require_trivially_destructible<S> &&
+                                              T::template require_will_fit_in_opaque_storage<S> &&
+                                              T::template require_alignment_compatible<S>);
+
+// `I` gives a count of sizeof(std::intptr_t) bytes , and `J` gives a raw count of bytes
+template <size_t I, size_t J = 0>
+struct KnownSizeFunctionObject {
+  using Data = std::array<std::byte, sizeof(std::intptr_t) * I + J>;
+  void operator()() const {};
+  Data data{};
+};
+
+}  // namespace
+
+// static_assert the expected type traits
+static_assert(std::is_invocable_r_v<void, ftl::Function<void()>>);
+static_assert(std::is_trivially_copyable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_destructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_assignable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_assignable_v<ftl::Function<void()>>);
+
+template <typename T>
+using function_traits = ftl::details::function_traits<T>;
+
+// static_assert that the expected value of N is used for known function object sizes.
+static_assert(function_traits<KnownSizeFunctionObject<0, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<0, 1>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 1>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 0>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 1>>::size == 2);
+
+// Check that is_function_v works
+static_assert(!ftl::is_function_v<KnownSizeFunctionObject<0>>);
+static_assert(!ftl::is_function_v<std::function<void()>>);
+static_assert(ftl::is_function_v<ftl::Function<void()>>);
+
+// static_assert what can and cannot be stored inside the opaque storage
+
+template <size_t N>
+using function_opaque_storage = ftl::details::function_opaque_storage<N>;
+
+// Function objects can be stored if they fit.
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<0>>);
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<1>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<2>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<2>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<3>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<3>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<4>>);
+
+// Another opaque storage can be stored if it fits. This property is used to copy smaller
+// ftl::Functions into larger ones.
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<0>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<1>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<2>::type>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<3>::type>);
+
+// Function objects that aren't trivially copyable or destroyable cannot be stored.
+auto lambda_capturing_unique_ptr = [ptr = std::unique_ptr<void*>()] { static_cast<void>(ptr); };
+static_assert(
+    !is_opaquely_storable<function_opaque_storage<2>, decltype(lambda_capturing_unique_ptr)>);
+
+// Keep in sync with "Example usage" in header file.
+TEST(Function, Example) {
+  using namespace std::string_view_literals;
+
+  class MyClass {
+   public:
+    void on_event() const {}
+    int on_string(int*, std::string_view) { return 1; }
+
+    auto get_function() {
+      return ftl::make_function([this] { on_event(); });
+    }
+  } cls;
+
+  // A function container with no arguments, and returning no value.
+  ftl::Function<void()> f;
+
+  // Construct a ftl::Function containing a small lambda.
+  f = cls.get_function();
+
+  // Construct a ftl::Function that calls `cls.on_event()`.
+  f = ftl::make_function<&MyClass::on_event>(&cls);
+
+  // Create a do-nothing function.
+  f = ftl::no_op;
+
+  // Invoke the contained function.
+  f();
+
+  // Also invokes it.
+  std::invoke(f);
+
+  // Create a typedef to give a more meaningful name and bound the size.
+  using MyFunction = ftl::Function<int(std::string_view), 2>;
+  int* ptr = nullptr;
+  auto f1 =
+      MyFunction::make([cls = &cls, ptr](std::string_view sv) { return cls->on_string(ptr, sv); });
+  int r = f1("abc"sv);
+
+  // Returns a default-constructed int (0).
+  f1 = ftl::no_op;
+  r = f1("abc"sv);
+  EXPECT_EQ(r, 0);
+}
+
+TEST(Function, BasicOperations) {
+  // Default constructible.
+  ftl::Function<int()> f;
+
+  // Compares as empty
+  EXPECT_FALSE(f);
+  EXPECT_TRUE(f == nullptr);
+  EXPECT_FALSE(f != nullptr);
+  EXPECT_TRUE(ftl::Function<int()>() == f);
+  EXPECT_FALSE(ftl::Function<int()>() != f);
+
+  // Assigning no_op sets it to not empty.
+  f = ftl::no_op;
+
+  // Verify it can be called, and that it returns a default constructed value.
+  EXPECT_EQ(f(), 0);
+
+  // Comparable when non-empty.
+  EXPECT_TRUE(f);
+  EXPECT_FALSE(f == nullptr);
+  EXPECT_TRUE(f != nullptr);
+  EXPECT_FALSE(ftl::Function<int()>() == f);
+  EXPECT_TRUE(ftl::Function<int()>() != f);
+
+  // Constructing from nullptr means empty.
+  f = ftl::Function<int()>{nullptr};
+  EXPECT_FALSE(f);
+
+  // Assigning nullptr means it is empty.
+  f = nullptr;
+  EXPECT_FALSE(f);
+
+  // Move construction
+  f = ftl::no_op;
+  ftl::Function<int()> g{std::move(f)};
+  EXPECT_TRUE(g != nullptr);
+
+  // Move assignment
+  f = nullptr;
+  f = std::move(g);
+  EXPECT_TRUE(f != nullptr);
+
+  // Copy construction
+  ftl::Function<int()> h{f};
+  EXPECT_TRUE(h != nullptr);
+
+  // Copy assignment
+  g = h;
+  EXPECT_TRUE(g != nullptr);
+}
+
+TEST(Function, CanMoveConstructFromLambda) {
+  auto lambda = [] {};
+  ftl::Function<void()> f{std::move(lambda)};
+}
+
+TEST(Function, TerseDeducedConstructAndAssignFromLambda) {
+  auto f = ftl::Function([] { return 1; });
+  EXPECT_EQ(f(), 1);
+
+  f = [] { return 2; };
+  EXPECT_EQ(f(), 2);
+}
+
+namespace {
+
+struct ImplicitConversionsHelper {
+  auto exact(int) -> int { return 0; }
+  auto inexact(long) -> short { return 0; }
+  // TODO: Switch to `auto templated(auto x)` with C++20
+  template <typename T>
+  T templated(T x) {
+    return x;
+  }
+
+  static auto static_exact(int) -> int { return 0; }
+  static auto static_inexact(long) -> short { return 0; }
+  // TODO: Switch to `static auto static_templated(auto x)` with C++20
+  template <typename T>
+  static T static_templated(T x) {
+    return x;
+  }
+};
+
+}  // namespace
+
+TEST(Function, ImplicitConversions) {
+  using Function = ftl::Function<int(int)>;
+  auto check = [](Function f) { return f(0); };
+  auto exact = [](int) -> int { return 0; };
+  auto inexact = [](long) -> short { return 0; };
+  auto templated = [](auto x) { return x; };
+
+  ImplicitConversionsHelper helper;
+
+  // Note, `check(nullptr)` would crash, so we can only check if it would be invocable.
+  static_assert(std::is_invocable_v<decltype(check), decltype(nullptr)>);
+
+  // Note: We invoke each of these to fully expand all the templates involved.
+  EXPECT_EQ(check(ftl::no_op), 0);
+
+  EXPECT_EQ(check(exact), 0);
+  EXPECT_EQ(check(inexact), 0);
+  EXPECT_EQ(check(templated), 0);
+
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::exact>(&helper)), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::inexact>(&helper)), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::templated<int>>(&helper)), 0);
+
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_exact>()), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_inexact>()), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_templated<int>>()), 0);
+}
+
+TEST(Function, MakeWithNonConstMemberFunction) {
+  struct Observer {
+    bool called = false;
+    void setCalled() { called = true; }
+  } observer;
+
+  auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstMemberFunction) {
+  struct Observer {
+    mutable bool called = false;
+    void setCalled() const { called = true; }
+  } observer;
+
+  const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstClassPointer) {
+  const struct Observer {
+    mutable bool called = false;
+    void setCalled() const { called = true; }
+  } observer;
+
+  const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithNonCapturingLambda) {
+  auto f = ftl::make_function([](int a, int b) { return a + b; });
+  EXPECT_EQ(f(1, 2), 3);
+}
+
+TEST(Function, MakeWithCapturingLambda) {
+  bool called = false;
+  auto f = ftl::make_function([&called](int a, int b) {
+    called = true;
+    return a + b;
+  });
+  EXPECT_EQ(f(1, 2), 3);
+  EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithCapturingMutableLambda) {
+  bool called = false;
+  auto f = ftl::make_function([&called](int a, int b) mutable {
+    called = true;
+    return a + b;
+  });
+  EXPECT_EQ(f(1, 2), 3);
+  EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithThreePointerCapturingLambda) {
+  bool my_bool = false;
+  int my_int = 0;
+  float my_float = 0.f;
+
+  auto f = ftl::make_function(
+      [ptr_bool = &my_bool, ptr_int = &my_int, ptr_float = &my_float](int a, int b) mutable {
+        *ptr_bool = true;
+        *ptr_int = 1;
+        *ptr_float = 1.f;
+
+        return a + b;
+      });
+
+  EXPECT_EQ(f(1, 2), 3);
+
+  EXPECT_TRUE(my_bool);
+  EXPECT_EQ(my_int, 1);
+  EXPECT_EQ(my_float, 1.f);
+}
+
+TEST(Function, MakeWithFreeFunction) {
+  auto f = ftl::make_function<&std::make_unique<int, int>>();
+  std::unique_ptr<int> unique_int = f(1);
+  ASSERT_TRUE(unique_int);
+  EXPECT_EQ(*unique_int, 1);
+}
+
+TEST(Function, CopyToLarger) {
+  int counter = 0;
+  ftl::Function<void()> a{[ptr_counter = &counter] { (*ptr_counter)++; }};
+  ftl::Function<void(), 1> b = a;
+  ftl::Function<void(), 2> c = a;
+
+  EXPECT_EQ(counter, 0);
+  a();
+  EXPECT_EQ(counter, 1);
+  b();
+  EXPECT_EQ(counter, 2);
+  c();
+  EXPECT_EQ(counter, 3);
+
+  b = [ptr_counter = &counter] { (*ptr_counter) += 2; };
+  c = [ptr_counter = &counter] { (*ptr_counter) += 3; };
+
+  b();
+  EXPECT_EQ(counter, 5);
+  c();
+  EXPECT_EQ(counter, 8);
+}
+
+}  // namespace android::test
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index ed5d5c1..394a000 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -575,17 +575,23 @@
         return mAngleNamespace;
     }
 
-    if (mAnglePath.empty() && !mShouldUseSystemAngle) {
-        ALOGV("mAnglePath is empty and not using system ANGLE, abort creating ANGLE namespace");
+    // If ANGLE path is not set, it means ANGLE should not be used for this process;
+    // or if ANGLE path is set and set to use system ANGLE, then a namespace is not needed
+    // because:
+    //     1) if the default OpenGL ES driver is already ANGLE, then the loader will skip;
+    //     2) if the default OpenGL ES driver is native, then there's no symbol conflict;
+    //     3) if there's no OpenGL ES driver is preloaded, then there's no symbol conflict.
+    if (mAnglePath.empty() || mShouldUseSystemAngle) {
+        ALOGV("mAnglePath is empty or use system ANGLE, abort creating ANGLE namespace");
         return nullptr;
     }
 
     // Construct the search paths for system ANGLE.
     const char* const defaultLibraryPaths =
 #if defined(__LP64__)
-            "/vendor/lib64/egl:/system/lib64/egl";
+            "/vendor/lib64/egl:/system/lib64";
 #else
-            "/vendor/lib/egl:/system/lib/egl";
+            "/vendor/lib/egl:/system/lib";
 #endif
 
     // If the application process will run on top of system ANGLE, construct the namespace
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
index 47607a0..9ebaf16 100644
--- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
@@ -104,7 +104,7 @@
         GL_UPDATED = 2,
         VULKAN = 3,
         VULKAN_UPDATED = 4,
-        ANGLE = 5,
+        ANGLE = 5, // cover both system ANGLE and ANGLE APK
     };
 
     enum Stats {
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index f17a654..eb4d3df 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -20,6 +20,25 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "libgui_flags",
+    package: "com.android.graphics.libgui.flags",
+    srcs: ["libgui_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libguiflags",
+    host_supported: true,
+    vendor_available: true,
+    min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
+    aconfig_declarations: "libgui_flags",
+}
+
 cc_library_headers {
     name: "libgui_headers",
     vendor_available: true,
@@ -36,6 +55,8 @@
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
     ],
+    static_libs: ["libguiflags"],
+    export_static_lib_headers: ["libguiflags"],
     min_sdk_version: "29",
     // TODO(b/218719284) can media use be constrained to libgui_bufferqueue_static?
     apex_available: [
@@ -192,19 +213,6 @@
     },
 }
 
-aconfig_declarations {
-    name: "libgui_flags",
-    package: "com.android.graphics.libgui.flags",
-    srcs: ["libgui_flags.aconfig"],
-}
-
-cc_aconfig_library {
-    name: "libguiflags",
-    host_supported: true,
-    vendor_available: true,
-    aconfig_declarations: "libgui_flags",
-}
-
 filegroup {
     name: "libgui-sources",
     srcs: [
@@ -265,6 +273,9 @@
         "libbinder",
         "libGLESv2",
     ],
+    export_static_lib_headers: [
+        "libguiflags",
+    ],
 }
 
 cc_library_shared {
@@ -415,7 +426,6 @@
         "libsync",
         "libui",
         "libutils",
-        "libvndksupport",
     ],
 
     static_libs: [
@@ -460,6 +470,7 @@
     static_libs: [
         "libgtest",
         "libgmock",
+        "libguiflags",
     ],
 
     srcs: [
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index dd0a028..f317a2e 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -26,7 +26,7 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
-#include <gui/Flags.h>
+
 #include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IProducerListener.h>
@@ -104,12 +104,11 @@
     }
 }
 
-void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
-                                                    const sp<Fence>& glDoneFence,
-                                                    const sp<Fence>& presentFence,
-                                                    const sp<Fence>& prevReleaseFence,
-                                                    CompositorTiming compositorTiming,
-                                                    nsecs_t latchTime, nsecs_t dequeueReadyTime) {
+void BLASTBufferItemConsumer::updateFrameTimestamps(
+        uint64_t frameNumber, uint64_t previousFrameNumber, nsecs_t refreshStartTime,
+        const sp<Fence>& glDoneFence, const sp<Fence>& presentFence,
+        const sp<Fence>& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime,
+        nsecs_t dequeueReadyTime) {
     Mutex::Autolock lock(mMutex);
 
     // if the producer is not connected, don't bother updating,
@@ -120,7 +119,15 @@
     std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(prevReleaseFence);
 
     mFrameEventHistory.addLatch(frameNumber, latchTime);
-    mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+    if (flags::frametimestamps_previousrelease()) {
+        if (previousFrameNumber > 0) {
+            mFrameEventHistory.addRelease(previousFrameNumber, dequeueReadyTime,
+                                          std::move(releaseFenceTime));
+        }
+    } else {
+        mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+    }
+
     mFrameEventHistory.addPreComposition(frameNumber, refreshStartTime);
     mFrameEventHistory.addPostComposition(frameNumber, glDoneFenceTime, presentFenceTime,
                                           compositorTiming);
@@ -144,7 +151,7 @@
     }
 }
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
 void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility,
                                              int8_t changeFrameRateStrategy) {
     sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
@@ -366,6 +373,7 @@
                 if (stat.latchTime > 0) {
                     mBufferItemConsumer
                             ->updateFrameTimestamps(stat.frameEventStats.frameNumber,
+                                                    stat.frameEventStats.previousFrameNumber,
                                                     stat.frameEventStats.refreshStartTime,
                                                     stat.frameEventStats.gpuCompositionDoneFence,
                                                     stat.presentFence, stat.previousReleaseFence,
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index ab0f6d2..b0f6e69 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -22,7 +22,6 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
-#include <gui/Flags.h>
 
 namespace android {
 
@@ -99,7 +98,7 @@
     }
 }
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
 void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility,
                                                         int8_t changeFrameRateStrategy) {
     sp<ConsumerListener> listener(mConsumerListener.promote());
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 5b34ba1..11f5174 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -36,9 +36,8 @@
 #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>
 #endif
 
 #include <system/window.h>
@@ -318,35 +317,44 @@
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
     BQ_LOGV("detachBuffer: slot %d", slot);
-    std::lock_guard<std::mutex> lock(mCore->mMutex);
+    sp<IProducerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
 
-    if (mCore->mIsAbandoned) {
-        BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
-        return NO_INIT;
+        if (mCore->mIsAbandoned) {
+            BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
+            return NO_INIT;
+        }
+
+        if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
+            BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
+            return BAD_VALUE;
+        }
+
+        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
+                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+            return BAD_VALUE;
+        } else if (!mSlots[slot].mBufferState.isAcquired()) {
+            BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
+                    "(state = %s)", slot, mSlots[slot].mBufferState.string());
+            return BAD_VALUE;
+        }
+        if (mCore->mBufferReleasedCbEnabled) {
+            listener = mCore->mConnectedProducerListener;
+        }
+
+        mSlots[slot].mBufferState.detachConsumer();
+        mCore->mActiveBuffers.erase(slot);
+        mCore->mFreeSlots.insert(slot);
+        mCore->clearBufferSlotLocked(slot);
+        mCore->mDequeueCondition.notify_all();
+        VALIDATE_CONSISTENCY();
     }
 
-    if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
-        BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
-        return BAD_VALUE;
+    if (listener) {
+        listener->onBufferDetached(slot);
     }
-
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
-        return BAD_VALUE;
-    } else if (!mSlots[slot].mBufferState.isAcquired()) {
-        BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
-                "(state = %s)", slot, mSlots[slot].mBufferState.string());
-        return BAD_VALUE;
-    }
-
-    mSlots[slot].mBufferState.detachConsumer();
-    mCore->mActiveBuffers.erase(slot);
-    mCore->mFreeSlots.insert(slot);
-    mCore->clearBufferSlotLocked(slot);
-    mCore->mDequeueCondition.notify_all();
-    VALIDATE_CONSISTENCY();
-
     return NO_ERROR;
 }
 
@@ -802,18 +810,14 @@
     const uid_t uid = BufferQueueThreadState::getCallingUid();
 #if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
     // permission check can't be done for vendors as vendors have no access to
-    // 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()) {
-        const pid_t pid = BufferQueueThreadState::getCallingPid();
-        if ((uid != shellUid) &&
-            !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
-            outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
-                                    "from pid=%d, uid=%d\n",
-                                    pid, uid);
-            denied = true;
-        }
+    // the PermissionController.
+    const pid_t pid = BufferQueueThreadState::getCallingPid();
+    if ((uid != shellUid) &&
+        !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
+        outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
+                                "from pid=%d, uid=%d\n",
+                                pid, uid);
+        denied = true;
     }
 #else
     if (uid != shellUid) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 67dff6d..19693e3 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -32,7 +32,7 @@
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
-#include <gui/Flags.h>
+
 #include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IConsumerListener.h>
@@ -1753,7 +1753,7 @@
     return NO_ERROR;
 }
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
 status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility,
                                            int8_t changeFrameRateStrategy) {
     ATRACE_CALL();
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 93df124..7d37fd3 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -344,6 +344,13 @@
     handleRefreshRateUpdates();
 }
 
+void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                              int32_t maxLevel) {
+    ALOGV("choreographer %p ~ received hdcp levels change event (displayId=%s, connectedLevel=%d, "
+          "maxLevel=%d), ignoring.",
+          this, to_string(displayId).c_str(), connectedLevel, maxLevel);
+}
+
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
         case MSG_SCHEDULE_CALLBACKS:
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 5dd058c..f3de96d 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -195,6 +195,11 @@
                     dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId,
                                                std::move(mFrameRateOverrides));
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+                    dispatchHdcpLevelsChanged(ev.header.displayId,
+                                              ev.hdcpLevelsChange.connectedLevel,
+                                              ev.hdcpLevelsChange.maxLevel);
+                    break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
                     break;
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 6993bfa..11524e2 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-#include <gui/Flags.h>
 #include <gui/FrameRateUtils.h>
 #include <system/window.h>
 #include <utils/Log.h>
 
 #include <cmath>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
+using namespace com::android::graphics::libgui;
 // Returns true if the frameRate is valid.
 //
 // @param frameRate the frame rate in Hz
@@ -53,7 +55,7 @@
             changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
             ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
                   changeFrameRateStrategy);
-            if (FLAG_BQ_SET_FRAME_RATE) {
+            if (flags::bq_setframerate()) {
                 return false;
             }
         }
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index d0c09e4..e81c098 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -28,7 +28,7 @@
 #include <binder/IInterface.h>
 
 #include <gui/BufferQueueDefs.h>
-#include <gui/Flags.h>
+
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
 #include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
@@ -763,7 +763,7 @@
         }
         return result;
     }
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     virtual status_t setFrameRate(float frameRate, int8_t compatibility,
                                   int8_t changeFrameRateStrategy) override {
         Parcel data, reply;
@@ -973,7 +973,7 @@
     return INVALID_OPERATION;
 }
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
 status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
                                               int8_t /*changeFrameRateStrategy*/) {
     // No-op for IGBP other than BufferQueue.
@@ -1522,7 +1522,7 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
         case SET_FRAME_RATE: {
             CHECK_INTERFACE(IGraphicBuffer, data, reply);
             float frameRate = data.readFloat();
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 29d64af..f5d19aa 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -25,6 +25,10 @@
 #include <gui/LayerState.h>
 #include <private/gui/ParcelUtils.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
+
 namespace android {
 
 namespace { // Anonymous
@@ -49,6 +53,11 @@
     status_t err = output->writeUint64(frameNumber);
     if (err != NO_ERROR) return err;
 
+    if (flags::frametimestamps_previousrelease()) {
+        err = output->writeUint64(previousFrameNumber);
+        if (err != NO_ERROR) return err;
+    }
+
     if (gpuCompositionDoneFence) {
         err = output->writeBool(true);
         if (err != NO_ERROR) return err;
@@ -79,6 +88,11 @@
     status_t err = input->readUint64(&frameNumber);
     if (err != NO_ERROR) return err;
 
+    if (flags::frametimestamps_previousrelease()) {
+        err = input->readUint64(&previousFrameNumber);
+        if (err != NO_ERROR) return err;
+    }
+
     bool hasFence = false;
     err = input->readBool(&hasFence);
     if (err != NO_ERROR) return err;
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/Surface.cpp b/libs/gui/Surface.cpp
index a87f053..07a0cfe 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -43,7 +43,7 @@
 
 #include <gui/AidlStatusUtil.h>
 #include <gui/BufferItem.h>
-#include <gui/Flags.h>
+
 #include <gui/IProducerListener.h>
 
 #include <gui/ISurfaceComposer.h>
@@ -2571,7 +2571,7 @@
 
 status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
                                int8_t changeFrameRateStrategy) {
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     if (flags::bq_setframerate()) {
         status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility,
                                                             changeFrameRateStrategy);
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a351811..8d18551 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2782,9 +2782,16 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
+status_t SurfaceComposerClient::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate);
+            ComposerServiceAIDL::getComposerService()->setGameModeFrameRateOverride(uid, frameRate);
+    return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setGameDefaultFrameRateOverride(uid_t uid, float frameRate) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->setGameDefaultFrameRateOverride(uid,
+                                                                                       frameRate);
     return statusTFromBinderStatus(status);
 }
 
@@ -3110,7 +3117,6 @@
             ->removeWindowInfosListener(windowInfosListener,
                                         ComposerServiceAIDL::getComposerService());
 }
-
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
@@ -3122,12 +3128,12 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t ScreenshotClient::captureDisplay(DisplayId displayId,
+status_t ScreenshotClient::captureDisplay(DisplayId displayId, const gui::CaptureArgs& captureArgs,
                                           const sp<IScreenCaptureListener>& captureListener) {
     sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    binder::Status status = s->captureDisplayById(displayId.value, captureListener);
+    binder::Status status = s->captureDisplayById(displayId.value, captureArgs, captureListener);
     return statusTFromBinderStatus(status);
 }
 
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 2eb6bd6..ba1d196 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -26,6 +26,41 @@
 
 namespace android::gui {
 
+namespace {
+
+std::ostream& operator<<(std::ostream& out, const sp<IBinder>& binder) {
+    if (binder == nullptr) {
+        out << "<null>";
+    } else {
+        out << binder.get();
+    }
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Region& region) {
+    if (region.isEmpty()) {
+        out << "<empty>";
+        return out;
+    }
+
+    bool first = true;
+    Region::const_iterator cur = region.begin();
+    Region::const_iterator const tail = region.end();
+    while (cur != tail) {
+        if (first) {
+            first = false;
+        } else {
+            out << "|";
+        }
+        out << "[" << cur->left << "," << cur->top << "][" << cur->right << "," << cur->bottom
+            << "]";
+        cur++;
+    }
+    return out;
+}
+
+} // namespace
+
 void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
     if (value) {
         inputConfig |= config;
@@ -66,8 +101,9 @@
 bool WindowInfo::operator==(const WindowInfo& info) const {
     return info.token == token && info.id == id && info.name == name &&
             info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
-            info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
-            info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
+            info.contentSize == contentSize && info.surfaceInset == surfaceInset &&
+            info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
+            info.touchableRegion.hasSameRects(touchableRegion) &&
             info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
             info.ownerUid == ownerUid && info.packageName == packageName &&
             info.inputConfig == inputConfig && info.displayId == displayId &&
@@ -101,6 +137,8 @@
         parcel->writeInt32(
                 static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
         parcel->write(frame) ?:
+        parcel->writeInt32(contentSize.width) ?:
+        parcel->writeInt32(contentSize.height) ?:
         parcel->writeInt32(surfaceInset) ?:
         parcel->writeFloat(globalScaleFactor) ?:
         parcel->writeFloat(alpha) ?:
@@ -150,6 +188,8 @@
     status = parcel->readInt32(&lpFlags) ?:
         parcel->readInt32(&lpType) ?:
         parcel->read(frame) ?:
+        parcel->readInt32(&contentSize.width) ?:
+        parcel->readInt32(&contentSize.height) ?:
         parcel->readInt32(&surfaceInset) ?:
         parcel->readFloat(&globalScaleFactor) ?:
         parcel->readFloat(&alpha) ?:
@@ -217,4 +257,24 @@
 void WindowInfoHandle::updateFrom(sp<WindowInfoHandle> handle) {
     mInfo = handle->mInfo;
 }
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
+    const WindowInfo& info = *window.getInfo();
+    std::string transform;
+    info.transform.dump(transform, "transform", "    ");
+    out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId
+        << ", inputConfig=" << info.inputConfig.string() << ", alpha=" << info.alpha << ", frame=["
+        << info.frame.left << "," << info.frame.top << "][" << info.frame.right << ","
+        << info.frame.bottom << "], globalScale=" << info.globalScaleFactor
+        << ", applicationInfo.name=" << info.applicationInfo.name
+        << ", applicationInfo.token=" << info.applicationInfo.token
+        << ", touchableRegion=" << info.touchableRegion << ", ownerPid=" << info.ownerPid.toString()
+        << ", ownerUid=" << info.ownerUid.toString() << ", dispatchingTimeout="
+        << std::chrono::duration_cast<std::chrono::milliseconds>(info.dispatchingTimeout).count()
+        << "ms, token=" << info.token.get()
+        << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n"
+        << transform;
+    return out;
+}
+
 } // namespace android::gui
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/aidl/android/gui/CaptureArgs.aidl
similarity index 77%
rename from libs/gui/include/gui/Flags.h
rename to libs/gui/aidl/android/gui/CaptureArgs.aidl
index a2cff56..920d949 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#pragma once
+package android.gui;
 
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index a7cf5dd..e3122bc 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -16,6 +16,7 @@
 
 package android.gui;
 
+import android.gui.CaptureArgs;
 import android.gui.Color;
 import android.gui.CompositionPreference;
 import android.gui.ContentSamplingAttributes;
@@ -238,7 +239,8 @@
      * Capture the specified screen. This requires the READ_FRAME_BUFFER
      * permission.
      */
-    oneway void captureDisplayById(long displayId, IScreenCaptureListener listener);
+    oneway void captureDisplayById(long displayId, in CaptureArgs args,
+            IScreenCaptureListener listener);
 
     /**
      * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
@@ -475,10 +477,21 @@
 
     /**
      * Set the override frame rate for a specified uid by GameManagerService.
+     * This override is controlled by game mode interventions.
+     * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
+     * in the LayerHistory.
+     */
+    void setGameModeFrameRateOverride(int uid, float frameRate);
+
+    /**
+     * Set the override frame rate for a specified uid by GameManagerService.
+     * This override is controlled by game default frame rate sysprop:
+     * "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+     * "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
      * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
      * in the scheduler.
      */
-    void setOverrideFrameRate(int uid, float frameRate);
+    void setGameDefaultFrameRateOverride(int uid, float frameRate);
 
     oneway void updateSmallAreaDetection(in int[] appIds, in float[] thresholds);
 
@@ -514,6 +527,13 @@
     void scheduleCommit();
 
     /**
+     * Force all window composition to the GPU (i.e. disable Hardware Overlays).
+     * This can help check if there is a bug in HW Composer.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void forceClientComposition(boolean enabled);
+
+    /**
      * Gets priority of the RenderEngine in SurfaceFlinger.
      */
     int getGpuContextPriority();
diff --git a/libs/gui/android/gui/TouchOcclusionMode.aidl b/libs/gui/android/gui/TouchOcclusionMode.aidl
index d91d052..ed72105 100644
--- a/libs/gui/android/gui/TouchOcclusionMode.aidl
+++ b/libs/gui/android/gui/TouchOcclusionMode.aidl
@@ -43,5 +43,6 @@
       * The window won't count for touch occlusion rules if the touch passes
       * through it.
       */
-    ALLOW
+    ALLOW,
+    ftl_last=ALLOW,
 }
diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
index 17f4c63..2e270b7 100644
--- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
@@ -141,8 +141,8 @@
     CompositorTiming compTiming;
     sp<Fence> previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING));
     sp<Fence> gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING));
-    FrameEventHistoryStats frameStats(frameNumber, gpuFence, compTiming,
-                                      mFdp.ConsumeIntegral<int64_t>(),
+    FrameEventHistoryStats frameStats(frameNumber, mFdp.ConsumeIntegral<uint64_t>(), gpuFence,
+                                      compTiming, mFdp.ConsumeIntegral<int64_t>(),
                                       mFdp.ConsumeIntegral<int64_t>());
     std::vector<SurfaceControlStats> stats;
     sp<Fence> presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 3142103..c952ba2 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -100,8 +100,8 @@
     MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
     MOCK_METHOD(binder::Status, captureDisplay,
                 (const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
-    MOCK_METHOD(binder::Status, captureDisplayById, (int64_t, const sp<IScreenCaptureListener>&),
-                (override));
+    MOCK_METHOD(binder::Status, captureDisplayById,
+                (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
     MOCK_METHOD(binder::Status, captureLayers,
                 (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
     MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
@@ -149,11 +149,13 @@
                 (const gui::Color&, const gui::Color&, float, float, float), (override));
     MOCK_METHOD(binder::Status, getDisplayDecorationSupport,
                 (const sp<IBinder>&, std::optional<gui::DisplayDecorationSupport>*), (override));
-    MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override));
+    MOCK_METHOD(binder::Status, setGameModeFrameRateOverride, (int32_t, float), (override));
+    MOCK_METHOD(binder::Status, setGameDefaultFrameRateOverride, (int32_t, float), (override));
     MOCK_METHOD(binder::Status, enableRefreshRateOverlay, (bool), (override));
     MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
     MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
     MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
+    MOCK_METHOD(binder::Status, forceClientComposition, (bool), (override));
     MOCK_METHOD(binder::Status, updateSmallAreaDetection,
                 (const std::vector<int32_t>&, const std::vector<float>&), (override));
     MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
@@ -205,6 +207,7 @@
     MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
     MOCK_METHOD3(dispatchFrameRateOverrides,
                  void(nsecs_t, PhysicalDisplayId, std::vector<FrameRateOverride>));
+    MOCK_METHOD3(dispatchHdcpLevelsChanged, void(PhysicalDisplayId, int32_t, int32_t));
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 02d7c4d..0e1a505 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -19,7 +19,7 @@
 
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
-#include <gui/Flags.h>
+
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/SurfaceComposerClient.h>
 
@@ -31,6 +31,8 @@
 #include <thread>
 #include <queue>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
 class BLASTBufferQueue;
@@ -48,8 +50,8 @@
     void onDisconnect() override EXCLUDES(mMutex);
     void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
                                   FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
-    void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
-                               const sp<Fence>& gpuCompositionDoneFence,
+    void updateFrameTimestamps(uint64_t frameNumber, uint64_t previousFrameNumber,
+                               nsecs_t refreshStartTime, const sp<Fence>& gpuCompositionDoneFence,
                                const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
                                CompositorTiming compositorTiming, nsecs_t latchTime,
                                nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
@@ -59,7 +61,7 @@
 
 protected:
     void onSidebandStreamChanged() override EXCLUDES(mMutex);
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     void onSetFrameRate(float frameRate, int8_t compatibility,
                         int8_t changeFrameRateStrategy) override;
 #endif
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 2756277..0948c4d0 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -19,11 +19,13 @@
 
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
-#include <gui/Flags.h>
+
 #include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
 class BufferQueue {
@@ -70,7 +72,7 @@
         void addAndGetFrameTimestamps(
                 const NewFrameEventsEntry* newTimestamps,
                 FrameEventHistoryDelta* outDelta) override;
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
         void onSetFrameRate(float frameRate, int8_t compatibility,
                             int8_t changeFrameRateStrategy) override;
 #endif
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 38805d0..de47483 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -18,7 +18,7 @@
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
 #include <gui/BufferQueueDefs.h>
-#include <gui/Flags.h>
+
 #include <gui/IGraphicBufferProducer.h>
 
 namespace android {
@@ -202,7 +202,7 @@
 
     // See IGraphicBufferProducer::setAutoPrerotation
     virtual status_t setAutoPrerotation(bool autoPrerotation);
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     // See IGraphicBufferProducer::setFrameRate
     status_t setFrameRate(float frameRate, int8_t compatibility,
                           int8_t changeFrameRateStrategy) override;
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 9fef512..55a7aa7 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -116,6 +116,8 @@
     void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
+    void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                   int32_t maxLevel) override;
 
     void scheduleCallbacks();
 
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/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index fe2dd20..82cd50c 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -65,6 +65,9 @@
     virtual void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                             std::vector<FrameRateOverride> overrides) = 0;
 
+    virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                           int32_t maxLevel) = 0;
+
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 79582ce..8c1103b 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -58,7 +58,6 @@
 // ----------------------------------------------------------------------------
 class DisplayEventReceiver {
 public:
-
     enum {
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
@@ -66,6 +65,7 @@
         DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
+        DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
     };
 
     struct Event {
@@ -101,12 +101,22 @@
             float frameRateHz __attribute__((aligned(8)));
         };
 
+        /*
+         * The values are defined in aidl:
+         * hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+         */
+        struct HdcpLevelsChange {
+            int32_t connectedLevel;
+            int32_t maxLevel;
+        };
+
         Header header;
         union {
             VSync vsync;
             Hotplug hotplug;
             ModeChange modeChange;
             FrameRateOverride frameRateOverride;
+            HdcpLevelsChange hdcpLevelsChange;
         };
     };
 
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index e183bf2..51d3959 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -19,13 +19,13 @@
 #include <binder/IInterface.h>
 #include <binder/SafeInterface.h>
 
-#include <gui/Flags.h>
-
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
 #include <cstdint>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
 class BufferItem;
@@ -93,7 +93,7 @@
     virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
                                           FrameEventHistoryDelta* /*outDelta*/) {}
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     // Notifies the consumer of a setFrameRate call from the producer side.
     virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
                                 int8_t /*changeFrameRateStrategy*/) {}
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 3562906..7639e70 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,7 +31,6 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
-#include <gui/Flags.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -42,6 +41,8 @@
 #include <optional>
 #include <vector>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 // ----------------------------------------------------------------------------
 
@@ -677,7 +678,7 @@
     // the width and height used for dequeueBuffer will be additionally swapped.
     virtual status_t setAutoPrerotation(bool autoPrerotation);
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     // Sets the apps intended frame rate.
     virtual status_t setFrameRate(float frameRate, int8_t compatibility,
                                   int8_t changeFrameRateStrategy);
diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h
index f7ffbb9..b15f501 100644
--- a/libs/gui/include/gui/IProducerListener.h
+++ b/libs/gui/include/gui/IProducerListener.h
@@ -49,6 +49,12 @@
     // onBuffersFreed is called from IGraphicBufferConsumer::discardFreeBuffers
     // to notify the producer that certain free buffers are discarded by the consumer.
     virtual void onBuffersDiscarded(const std::vector<int32_t>& slots) = 0; // Asynchronous
+    // onBufferDetached is called from IGraphicBufferConsumer::detachBuffer to
+    // notify the producer that a buffer slot is free and ready to be dequeued.
+    //
+    // This is called without any lock held and can be called concurrently by
+    // multiple threads.
+    virtual void onBufferDetached(int /*slot*/) {} // Asynchronous
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 364a57b..bc97cd0 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -95,15 +95,18 @@
     status_t readFromParcel(const Parcel* input) override;
 
     FrameEventHistoryStats() = default;
-    FrameEventHistoryStats(uint64_t fn, const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
+    FrameEventHistoryStats(uint64_t frameNumber, uint64_t previousFrameNumber,
+                           const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
                            nsecs_t refreshTime, nsecs_t dequeueReadyTime)
-          : frameNumber(fn),
+          : frameNumber(frameNumber),
+            previousFrameNumber(previousFrameNumber),
             gpuCompositionDoneFence(gpuCompFence),
             compositorTiming(compTiming),
             refreshStartTime(refreshTime),
             dequeueReadyTime(dequeueReadyTime) {}
 
     uint64_t frameNumber;
+    uint64_t previousFrameNumber;
     sp<Fence> gpuCompositionDoneFence;
     CompositorTiming compositorTiming;
     nsecs_t refreshStartTime;
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/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 54c3aa7..14e3dd5 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -201,7 +201,13 @@
 
     // Sets the frame rate of a particular app (uid). This is currently called
     // by GameManager.
-    static status_t setOverrideFrameRate(uid_t uid, float frameRate);
+    static status_t setGameModeFrameRateOverride(uid_t uid, float frameRate);
+
+    // Sets the frame rate of a particular app (uid). This is currently called
+    // by GameManager and controlled by two sysprops:
+    // "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+    // "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
+    static status_t setGameDefaultFrameRateOverride(uid_t uid, float frameRate);
 
     // Update the small area detection whole appId-threshold mappings by same size appId and
     // threshold vector.
@@ -841,8 +847,14 @@
 class ScreenshotClient {
 public:
     static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
-    static status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
+                                   const sp<IScreenCaptureListener>&);
     static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+
+    [[deprecated]] static status_t captureDisplay(DisplayId id,
+                                                  const sp<IScreenCaptureListener>& listener) {
+        return captureDisplay(id, gui::CaptureArgs(), listener);
+    }
 };
 
 // ---------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index bd2eb74..b72b71a 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -26,6 +26,7 @@
 #include <gui/constants.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/Size.h>
 #include <ui/Transform.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
@@ -175,6 +176,8 @@
                 static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
         CLONE =
                 static_cast<uint32_t>(os::InputConfig::CLONE),
+        GLOBAL_STYLUS_BLOCKS_TOUCH =
+                static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH),
         // clang-format on
     };
 
@@ -196,6 +199,9 @@
     /* These values are filled in by SurfaceFlinger. */
     Rect frame = Rect::INVALID_RECT;
 
+    // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+    ui::Size contentSize = ui::Size(0, 0);
+
     /*
      * SurfaceFlinger consumes this value to shrink the computed frame. This is
      * different from shrinking the touchable region in that it DOES shift the coordinate
@@ -311,4 +317,7 @@
 
     WindowInfo mInfo;
 };
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window);
+
 } // namespace android::gui
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/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index a16be78..b081030 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -8,3 +8,10 @@
   is_fixed_read_only: true
 }
 
+flag {
+  name: "frametimestamps_previousrelease"
+  namespace: "core_graphics"
+  description: "Controls a fence fixup for timestamp apis"
+  bug: "310927247"
+  is_fixed_read_only: true
+}
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 38c0eed..6dcd501 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,7 +21,7 @@
         "-Wall",
         "-Werror",
         "-Wno-extra",
-        "-DFLAG_BQ_SET_FRAME_RATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
     ],
 
     srcs: [
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 9893c71..ea7078d 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -42,9 +42,12 @@
 
 #include <gtest/gtest.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 using namespace std::chrono_literals;
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 using Transaction = SurfaceComposerClient::Transaction;
 using android::hardware::graphics::common::V1_2::BufferUsage;
@@ -1581,6 +1584,9 @@
     nsecs_t postedTimeB = 0;
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeB, &postedTimeB, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(2);
+
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
 
@@ -1590,7 +1596,9 @@
     ASSERT_GE(events->postedTime, postedTimeA);
 
     ASSERT_GE(events->latchTime, postedTimeA);
-    ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
     ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
     ASSERT_NE(nullptr, events->displayPresentFence);
     ASSERT_NE(nullptr, events->releaseFence);
@@ -1602,6 +1610,50 @@
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
 
+    // Now do the same as above with a third buffer, so that timings related to
+    // buffer releases make it back to the first frame.
+    nsecs_t requestedPresentTimeC = 0;
+    nsecs_t postedTimeC = 0;
+    setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
+    history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(3);
+
+    // Check the first frame...
+    events = history.getFrame(1);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeA);
+    ASSERT_GE(events->latchTime, postedTimeA);
+    // Now dequeueReadyTime is valid, because the release timings finally
+    // propaged to queueBuffer()
+    ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // ...and the second
+    events = history.getFrame(2);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeB);
+    ASSERT_GE(events->latchTime, postedTimeB);
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // ...and finally the third!
+    events = history.getFrame(3);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(3, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeC);
+
     // wait for any callbacks that have not been received
     adapter.waitForCallbacks();
 }
@@ -1660,6 +1712,8 @@
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
 
+    adapter.waitForCallback(3);
+
     // frame number, requestedPresentTime, and postTime should not have changed
     ASSERT_EQ(1, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
@@ -1679,6 +1733,42 @@
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
+
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // Queue another buffer to check for timestamps that came late
+    nsecs_t requestedPresentTimeD = 0;
+    nsecs_t postedTimeD = 0;
+    setUpAndQueueBuffer(igbProducer, &requestedPresentTimeD, &postedTimeD, &qbOutput, true);
+    history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(4);
+
+    // frame number, requestedPresentTime, and postTime should not have changed
+    events = history.getFrame(1);
+    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeA);
+
+    // a valid latchtime and pre and post composition info should not be set for the dropped frame
+    ASSERT_FALSE(events->hasLatchInfo());
+    ASSERT_FALSE(events->hasDequeueReadyInfo());
+    ASSERT_FALSE(events->hasGpuCompositionDoneInfo());
+    ASSERT_FALSE(events->hasDisplayPresentInfo());
+    ASSERT_FALSE(events->hasReleaseInfo());
+
+    // we should also have gotten values for the presented frame
+    events = history.getFrame(2);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeB);
+    ASSERT_GE(events->latchTime, postedTimeB);
     ASSERT_GE(events->dequeueReadyTime, events->latchTime);
     ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
     ASSERT_NE(nullptr, events->displayPresentFence);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 17aa5f1..df7739c 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -1187,6 +1187,76 @@
     ASSERT_EQ(true, output.bufferReplaced);
 }
 
+struct BufferDetachedListener : public BnProducerListener {
+public:
+    BufferDetachedListener() = default;
+    virtual ~BufferDetachedListener() = default;
+
+    virtual void onBufferReleased() {}
+    virtual bool needsReleaseNotify() { return true; }
+    virtual void onBufferDetached(int slot) {
+        mDetachedSlots.push_back(slot);
+    }
+    const std::vector<int>& getDetachedSlots() const { return mDetachedSlots; }
+private:
+    std::vector<int> mDetachedSlots;
+};
+
+TEST_F(BufferQueueTest, TestConsumerDetachProducerListener) {
+    createBufferQueue();
+    sp<MockConsumer> mc(new MockConsumer);
+    ASSERT_EQ(OK, mConsumer->consumerConnect(mc, true));
+    IGraphicBufferProducer::QueueBufferOutput output;
+    sp<BufferDetachedListener> pl(new BufferDetachedListener);
+    ASSERT_EQ(OK, mProducer->connect(pl, NATIVE_WINDOW_API_CPU, true, &output));
+    ASSERT_EQ(OK, mProducer->setDequeueTimeout(0));
+    ASSERT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1));
+
+    sp<Fence> fence = Fence::NO_FENCE;
+    sp<GraphicBuffer> buffer = nullptr;
+    IGraphicBufferProducer::QueueBufferInput input(0ull, true,
+        HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
+        NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+
+    int slots[2] = {};
+    status_t result = OK;
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
+
+    result = mProducer->dequeueBuffer(&slots[0], &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slots[0], &buffer));
+
+    result = mProducer->dequeueBuffer(&slots[1], &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slots[1], &buffer));
+
+    // Queue & detach one from two dequeued buffes.
+    ASSERT_EQ(OK, mProducer->queueBuffer(slots[1], input, &output));
+    BufferItem item{};
+    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+    ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
+
+    // Check whether the slot from IProducerListener is same to the detached slot.
+    ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+    ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
+
+    // Dequeue another buffer.
+    int slot;
+    result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+    // Dequeue should fail here, since we dequeued 3 buffers and one buffer was
+    // detached from consumer(Two buffers are dequeued, and the current max
+    // dequeued buffer count is two).
+    result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_TRUE(result == WOULD_BLOCK || result == TIMED_OUT || result == INVALID_OPERATION);
+}
+
 TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
     createBufferQueue();
     sp<MockConsumer> mc(new MockConsumer);
@@ -1266,9 +1336,7 @@
 }
 
 TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) {
-    if (flags::bq_setframerate()) {
-        ASSERT_EQ(true, FLAG_BQ_SET_FRAME_RATE);
-    }
+    ASSERT_EQ(flags::bq_setframerate(), COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE));
 }
 
 struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index e7b1232..c6ea317 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -208,21 +208,6 @@
         ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
     }
 
-    static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
-                                   ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerServiceAIDL::getComposerService();
-        SurfaceComposerClient::Transaction().apply(true);
-
-        const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
-        status_t err = gui::aidl_utils::statusTFromBinderStatus(status);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        captureResults = captureListener->waitForResults();
-        return fenceStatus(captureResults.fenceResult);
-    }
-
     sp<Surface> mSurface;
     sp<SurfaceComposerClient> mComposerClient;
     sp<SurfaceControl> mSurfaceControl;
@@ -260,56 +245,6 @@
     EXPECT_EQ(1, result);
 }
 
-// This test probably doesn't belong here.
-TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) {
-    sp<ANativeWindow> anw(mSurface);
-
-    // Verify the screenshot works with no protected buffers.
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    // display 0 is picked for now, can extend to support all displays if needed
-    const sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    ASSERT_FALSE(display == nullptr);
-
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = display;
-    captureArgs.width = 64;
-    captureArgs.height = 64;
-
-    ScreenCaptureResults captureResults;
-    ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-
-    ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(),
-            NATIVE_WINDOW_API_CPU));
-    // Set the PROTECTED usage bit and verify that the screenshot fails.  Note
-    // that we need to dequeue a buffer in order for it to actually get
-    // allocated in SurfaceFlinger.
-    ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(),
-            GRALLOC_USAGE_PROTECTED));
-    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(anw.get(), 3));
-    ANativeWindowBuffer* buf = nullptr;
-
-    status_t err = native_window_dequeue_buffer_and_wait(anw.get(), &buf);
-    if (err) {
-        // we could fail if GRALLOC_USAGE_PROTECTED is not supported.
-        // that's okay as long as this is the reason for the failure.
-        // try again without the GRALLOC_USAGE_PROTECTED bit.
-        ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), 0));
-        ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
-                &buf));
-        return;
-    }
-    ASSERT_EQ(NO_ERROR, anw->cancelBuffer(anw.get(), buf, -1));
-
-    for (int i = 0; i < 4; i++) {
-        // Loop to make sure SurfaceFlinger has retired a protected buffer.
-        ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
-                &buf));
-        ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1));
-    }
-    ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-}
-
 TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
     sp<ANativeWindow> anw(mSurface);
     int result = -123;
@@ -851,7 +786,8 @@
         return binder::Status::ok();
     }
 
-    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override {
+    binder::Status captureDisplayById(int64_t, const gui::CaptureArgs&,
+                                      const sp<IScreenCaptureListener>&) override {
         return binder::Status::ok();
     }
 
@@ -985,7 +921,11 @@
         return binder::Status::ok();
     }
 
-    binder::Status setOverrideFrameRate(int32_t /*uid*/, float /*frameRate*/) override {
+    binder::Status setGameModeFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setGameDefaultFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
         return binder::Status::ok();
     }
 
@@ -999,6 +939,10 @@
 
     binder::Status scheduleCommit() override { return binder::Status::ok(); }
 
+    binder::Status forceClientComposition(bool /*enabled*/) override {
+        return binder::Status::ok();
+    }
+
     binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
                                             const std::vector<float>& /*thresholds*/) {
         return binder::Status::ok();
@@ -1341,7 +1285,7 @@
                 newFrame->mRefreshes[0].mGpuCompositionDone.mFenceTime :
                 FenceTime::NO_FENCE;
         // HWC2 releases the previous buffer after a new latch just before
-        // calling postComposition.
+        // calling onCompositionPresented.
         if (oldFrame != nullptr) {
             mCfeh->addRelease(nOldFrame, oldFrame->kDequeueReadyTime,
                     std::shared_ptr<FenceTime>(oldFrame->mRelease.mFenceTime));
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index f2feaef..5eb5d3b 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -28,6 +28,7 @@
 using gui::InputApplicationInfo;
 using gui::TouchOcclusionMode;
 using gui::WindowInfo;
+using ui::Size;
 
 namespace test {
 
@@ -53,6 +54,7 @@
     i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
     i.dispatchingTimeout = 12s;
     i.frame = Rect(93, 34, 16, 19);
+    i.contentSize = Size(10, 40);
     i.surfaceInset = 17;
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
@@ -83,6 +85,7 @@
     ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
     ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
     ASSERT_EQ(i.frame, i2.frame);
+    ASSERT_EQ(i.contentSize, i2.contentSize);
     ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
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/Android.bp b/libs/input/Android.bp
index 69a4f0a..dd8dc8d 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -37,20 +37,20 @@
 // flags
 /////////////////////////////////////////////////
 aconfig_declarations {
-    name: "aconfig_input_flags",
+    name: "com.android.input.flags-aconfig",
     package: "com.android.input.flags",
     srcs: ["input_flags.aconfig"],
 }
 
 cc_aconfig_library {
-    name: "aconfig_input_flags_c_lib",
-    aconfig_declarations: "aconfig_input_flags",
+    name: "com.android.input.flags-aconfig-cc",
+    aconfig_declarations: "com.android.input.flags-aconfig",
     host_supported: true,
     // Use the test version of the aconfig flag library by default to allow tests to set local
     // overrides for flags, without having to link against a separate version of libinput or of this
     // library. Bundling this library directly into libinput prevents us from having to add this
     // library as a shared lib dependency everywhere where libinput is used.
-    test: true,
+    mode: "test",
     shared: {
         enabled: false,
     },
@@ -242,7 +242,7 @@
     ],
 
     whole_static_libs: [
-        "aconfig_input_flags_c_lib",
+        "com.android.input.flags-aconfig-cc",
         "libinput_rust_ffi",
     ],
 
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index bd5b67b..8eaff00 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -379,6 +379,11 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const PointerProperties& properties) {
+    out << "Pointer(id=" << properties.id << ", " << ftl::enum_string(properties.toolType) << ")";
+    return out;
+}
+
 // --- PointerCoords ---
 
 float PointerCoords::getAxisValue(int32_t axis) const {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 83453af..598f949 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -447,7 +447,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) {
@@ -479,7 +479,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) {
@@ -568,7 +568,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));
@@ -663,7 +663,7 @@
               "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
               "metaState=0x%x, buttonState=0x%x, classification=%s,"
               "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
-              "pointerCount=%" PRIu32 " \n%s",
+              "pointerCount=%" PRIu32 "\n%s",
               mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
               inputEventSourceToString(source).c_str(), displayId,
               MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index a4cd239..e2feabc 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -613,14 +613,14 @@
 }
 
 #ifdef __linux__
-std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
     if (parcel == nullptr) {
         ALOGE("%s: Null parcel", __func__);
         return nullptr;
     }
     std::string loadFileName = parcel->readCString();
-    std::shared_ptr<KeyCharacterMap> map =
-            std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap(loadFileName));
+    std::unique_ptr<KeyCharacterMap> map =
+            std::make_unique<KeyCharacterMap>(KeyCharacterMap(loadFileName));
     map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
     map->mLayoutOverlayApplied = parcel->readBool();
     size_t numKeys = parcel->readInt32();
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/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 116b778..613a0df 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -275,10 +275,10 @@
     }
 }
 
-void VelocityTracker::addMovement(const MotionEvent* event) {
+void VelocityTracker::addMovement(const MotionEvent& event) {
     // Stores data about which axes to process based on the incoming motion event.
     std::set<int32_t> axesToProcess;
-    int32_t actionMasked = event->getActionMasked();
+    int32_t actionMasked = event.getActionMasked();
 
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_DOWN:
@@ -291,7 +291,7 @@
             // Start a new movement trace for a pointer that just went down.
             // We do this on down instead of on up because the client may want to query the
             // final velocity for a pointer that just went up.
-            clearPointer(event->getPointerId(event->getActionIndex()));
+            clearPointer(event.getPointerId(event.getActionIndex()));
             axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
             break;
         }
@@ -300,8 +300,14 @@
             axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
             break;
         case AMOTION_EVENT_ACTION_POINTER_UP:
+            if (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) {
+                clearPointer(event.getPointerId(event.getActionIndex()));
+                return;
+            }
+            // Continue to ACTION_UP to ensure that the POINTER_STOPPED logic is triggered.
+            [[fallthrough]];
         case AMOTION_EVENT_ACTION_UP: {
-            std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
+            std::chrono::nanoseconds delaySinceLastEvent(event.getEventTime() - mLastEventTime);
             if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
                 ALOGD_IF(DEBUG_VELOCITY,
                          "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
@@ -325,21 +331,26 @@
         case AMOTION_EVENT_ACTION_SCROLL:
             axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
             break;
+        case AMOTION_EVENT_ACTION_CANCEL: {
+            clear();
+            return;
+        }
+
         default:
             // Ignore all other actions.
             return;
     }
 
-    const size_t historySize = event->getHistorySize();
+    const size_t historySize = event.getHistorySize();
     for (size_t h = 0; h <= historySize; h++) {
-        const nsecs_t eventTime = event->getHistoricalEventTime(h);
-        for (size_t i = 0; i < event->getPointerCount(); i++) {
-            if (event->isResampled(i, h)) {
+        const nsecs_t eventTime = event.getHistoricalEventTime(h);
+        for (size_t i = 0; i < event.getPointerCount(); i++) {
+            if (event.isResampled(i, h)) {
                 continue; // skip resampled samples
             }
-            const int32_t pointerId = event->getPointerId(i);
+            const int32_t pointerId = event.getPointerId(i);
             for (int32_t axis : axesToProcess) {
-                const float position = event->getHistoricalAxisValue(axis, i, h);
+                const float position = event.getHistoricalAxisValue(axis, i, h);
                 addMovement(eventTime, pointerId, axis, position);
             }
         }
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 9a459b1..db7031a 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -193,6 +193,7 @@
         {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
         {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
         {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+        {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
 };
 VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
 VirtualKeyboard::~VirtualKeyboard() {}
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 4e644ff..5d39155 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -150,4 +150,11 @@
      * likely a duplicate window with the same client token, but different bounds.
      */
     CLONE                        = 1 << 16,
+
+    /**
+     * If the stylus is currently down *anywhere* on the screen, new touches will not be delivered
+     * to the window with this flag. This helps prevent unexpected clicks on some system windows,
+     * like StatusBar and TaskBar.
+     */
+    GLOBAL_STYLUS_BLOCKS_TOUCH   = 1 << 17,
 }
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 6302ff5..11f6994 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -41,3 +41,59 @@
   description: "Brings back fatal logging for inconsistent event streams originating from accessibility."
   bug: "299977100"
 }
+
+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: "Enabling additional touchpad palm rejection will disable the tap to click while the user is typing on a physical keyboard"
+  bug: "301055381"
+}
+
+flag {
+  name: "enable_v2_touchpad_typing_palm_rejection"
+  namespace: "input"
+  description: "In addition to touchpad palm rejection v1, v2 will also cancel ongoing move gestures while typing and add delay in re-enabling the tap to click."
+  bug: "301055381"
+}
+
+flag {
+  name: "remove_app_switch_drops"
+  namespace: "input"
+  description: "Remove the logic of dropping events due to pending app switch"
+  bug: "284808102"
+}
+
+flag {
+  name: "disable_reject_touch_on_stylus_hover"
+  namespace: "input"
+  description: "Disable touch rejection when the stylus hovers the screen"
+  bug: "301216095"
+}
+
+flag {
+  name: "enable_input_filter_rust_impl"
+  namespace: "input"
+  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 bbc6d98..867af79 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -118,18 +118,14 @@
 
         match action.into() {
             MotionAction::Down => {
-                let it = self
-                    .touching_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
-                let pointer_id = pointer_properties[0].id;
-                if it.contains(&pointer_id) {
+                if self.touching_pointer_ids_by_device.contains_key(&device_id) {
                     return Err(format!(
                         "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
-                        self.name, device_id, it
+                        self.name, device_id, self.touching_pointer_ids_by_device
                     ));
                 }
-                it.insert(pointer_id);
+                let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
+                it.insert(pointer_properties[0].id);
             }
             MotionAction::PointerDown { action_index } => {
                 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
@@ -225,19 +221,13 @@
                         self.name, device_id, self.hovering_pointer_ids_by_device
                     ));
                 }
-                let it = self
-                    .hovering_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
+                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
                 it.insert(pointer_properties[0].id);
             }
             MotionAction::HoverMove => {
                 // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
                 // If there was no prior HOVER_ENTER, just start a new hovering pointer.
-                let it = self
-                    .hovering_pointer_ids_by_device
-                    .entry(device_id)
-                    .or_insert_with(HashSet::new);
+                let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
                 it.insert(pointer_properties[0].id);
             }
             MotionAction::HoverExit => {
@@ -282,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) {
@@ -353,6 +347,56 @@
     }
 
     #[test]
+    fn two_pointer_stream() {
+        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());
+        // POINTER 0 UP
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
+                    | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                &two_pointer_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+        // ACTION_UP for pointer id=1
+        let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                Source::Touchscreen,
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_1_properties,
+                MotionFlags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
     fn multi_device_stream() {
         let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
         let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
@@ -552,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/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 1c8ec90..f9ca280 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -229,41 +229,23 @@
     return events;
 }
 
-static std::optional<float> computePlanarVelocity(
-        const VelocityTracker::Strategy strategy,
-        const std::vector<PlanarMotionEventEntry>& motions, int32_t axis,
-        uint32_t pointerId = DEFAULT_POINTER_ID) {
+static std::optional<float> computeVelocity(const VelocityTracker::Strategy strategy,
+                                            const std::vector<MotionEvent>& events, int32_t axis,
+                                            uint32_t pointerId = DEFAULT_POINTER_ID) {
     VelocityTracker vt(strategy);
 
-    std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
-    for (MotionEvent event : events) {
-        vt.addMovement(&event);
+    for (const MotionEvent& event : events) {
+        vt.addMovement(event);
     }
 
     return vt.getVelocity(axis, pointerId);
 }
 
-static std::vector<MotionEvent> createMotionEventStream(
-        int32_t axis, const std::vector<std::pair<std::chrono::nanoseconds, float>>& motion) {
-    switch (axis) {
-        case AMOTION_EVENT_AXIS_SCROLL:
-            return createAxisScrollMotionEventStream(motion);
-        default:
-            ADD_FAILURE() << "Axis " << axis << " is not supported";
-            return {};
-    }
-}
-
-static std::optional<float> computeVelocity(
+static std::optional<float> computePlanarVelocity(
         const VelocityTracker::Strategy strategy,
-        const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions, int32_t axis) {
-    VelocityTracker vt(strategy);
-
-    for (const MotionEvent& event : createMotionEventStream(axis, motions)) {
-        vt.addMovement(&event);
-    }
-
-    return vt.getVelocity(axis, DEFAULT_POINTER_ID);
+        const std::vector<PlanarMotionEventEntry>& motions, int32_t axis, uint32_t pointerId) {
+    std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+    return computeVelocity(strategy, events, axis, pointerId);
 }
 
 static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
@@ -277,23 +259,23 @@
         const VelocityTracker::Strategy strategy,
         const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions,
         std::optional<float> targetVelocity) {
-    checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity);
+    std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+    checkVelocity(computeVelocity(strategy, events, AMOTION_EVENT_AXIS_SCROLL), targetVelocity);
     // The strategy LSQ2 is not compatible with AXIS_SCROLL. In those situations, we should fall
     // back to a strategy that supports differential axes.
-    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, motions,
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events,
                                   AMOTION_EVENT_AXIS_SCROLL),
                   targetVelocity);
 }
 
 static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions,
                                              float velocity) {
-    VelocityTracker vt(VelocityTracker::Strategy::LSQ2);
-    std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
-    for (MotionEvent event : events) {
-        vt.addMovement(&event);
-    }
-    std::optional<float> velocityX = vt.getVelocity(AMOTION_EVENT_AXIS_X, 0);
-    std::optional<float> velocityY = vt.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+    std::optional<float> velocityX =
+            computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+                                  DEFAULT_POINTER_ID);
+    std::optional<float> velocityY =
+            computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+                                  DEFAULT_POINTER_ID);
     ASSERT_TRUE(velocityX);
     ASSERT_TRUE(velocityY);
 
@@ -330,12 +312,14 @@
                                                    {30ms, {{6, 20}}},
                                                    {40ms, {{10, 30}}}};
 
-    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X),
+    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+                                    DEFAULT_POINTER_ID),
               computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
-                                    AMOTION_EVENT_AXIS_X));
-    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y),
+                                    AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
+    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+                                    DEFAULT_POINTER_ID),
               computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
-                                    AMOTION_EVENT_AXIS_Y));
+                                    AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID));
 }
 
 TEST_F(VelocityTrackerTest, TestComputedVelocity) {
@@ -431,7 +415,7 @@
     VelocityTracker vt(VelocityTracker::Strategy::IMPULSE);
     std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
     for (const MotionEvent& event : events) {
-        vt.addMovement(&event);
+        vt.addMovement(event);
     }
 
     float maxFloat = std::numeric_limits<float>::max();
@@ -509,6 +493,89 @@
     computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 500);
 }
 
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionCancelResultsInZeroVelocity) {
+    std::vector<PlanarMotionEventEntry> motions = {
+            {0ms, {{0, 0}}},    // DOWN
+            {10ms, {{5, 10}}},  // MOVE
+            {20ms, {{10, 20}}}, // MOVE
+            {20ms, {{10, 20}}}, // ACTION_UP
+    };
+    std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+    // By default, `createTouchMotionEventStream` produces an event stream that terminates with
+    // ACTION_UP. We need to manually change it to ACTION_CANCEL.
+    MotionEvent& lastEvent = events.back();
+    lastEvent.setAction(AMOTION_EVENT_ACTION_CANCEL);
+    lastEvent.setFlags(lastEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+    const int32_t pointerId = lastEvent.getPointerId(0);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+}
+
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionPointerCancelResultsInZeroVelocityForThatPointer) {
+    std::vector<PlanarMotionEventEntry> motions = {
+            {0ms, {{0, 5}, {NAN, NAN}}},    // DOWN
+            {0ms, {{0, 5}, {10, 15}}},      // POINTER_DOWN
+            {10ms, {{5, 10}, {15, 20}}},    // MOVE
+            {20ms, {{10, 15}, {20, 25}}},   // MOVE
+            {30ms, {{10, 15}, {20, 25}}},   // POINTER_UP
+            {30ms, {{10, 15}, {NAN, NAN}}}, // UP
+    };
+    std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+    // Cancel the lifting pointer of the ACTION_POINTER_UP event
+    MotionEvent& pointerUpEvent = events.rbegin()[1];
+    pointerUpEvent.setFlags(pointerUpEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+    const int32_t pointerId = pointerUpEvent.getPointerId(pointerUpEvent.getActionIndex());
+    // Double check the stream
+    ASSERT_EQ(1, pointerId);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP, pointerUpEvent.getActionMasked());
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, events.back().getActionMasked());
+
+    // Ensure the velocity of the lifting pointer is zero
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+                                  pointerId),
+                  /*targetVelocity*/ std::nullopt);
+
+    // The remaining pointer should have the correct velocity.
+    const int32_t remainingPointerId = events.back().getPointerId(0);
+    ASSERT_EQ(0, remainingPointerId);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+                                  remainingPointerId),
+                  /*targetVelocity*/ 500);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+                                  remainingPointerId),
+                  /*targetVelocity*/ 500);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+                                  remainingPointerId),
+                  /*targetVelocity*/ 500);
+    checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+                                  remainingPointerId),
+                  /*targetVelocity*/ 500);
+}
 
 /**
  * ================== VelocityTracker tests generated by recording real events =====================
@@ -1336,9 +1403,10 @@
             {40ms, 100},
     };
 
-    EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, motions,
+    std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+    EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, events,
                               AMOTION_EVENT_AXIS_SCROLL),
-              computeVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+              computeVelocity(VelocityTracker::Strategy::DEFAULT, events,
                               AMOTION_EVENT_AXIS_SCROLL));
 }
 
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 32fb350..099f47d 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,7 +19,7 @@
 #include <android/hardware_buffer.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/ConsumerBase.h>
-#include <gui/Flags.h>
+
 #include <gui/IGraphicBufferProducer.h>
 #include <sys/cdefs.h>
 #include <system/graphics.h>
@@ -352,7 +352,7 @@
     /**
      * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
      */
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
     void onSetFrameRate(float frameRate, int8_t compatibility,
                         int8_t changeFrameRateStrategy) override;
 #endif
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index c2535e0..3a09204 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -515,7 +515,7 @@
     }
 }
 
-#if FLAG_BQ_SET_FRAME_RATE
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
 void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
                                     int8_t changeFrameRateStrategy) {
     SFT_LOGV("onSetFrameRate: %.2f", frameRate);
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index e7b2195..745b6f3 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -113,6 +113,22 @@
                 AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
         "HAL and AHardwareBuffer pixel format don't match");
 
+static enum AHardwareBufferStatus filterStatus(status_t status) {
+    switch (status) {
+        case STATUS_OK:
+            return AHARDWAREBUFFER_STATUS_OK;
+        case STATUS_NO_MEMORY:
+            return AHARDWAREBUFFER_STATUS_NO_MEMORY;
+        case STATUS_BAD_VALUE:
+            return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+        case STATUS_UNKNOWN_TRANSACTION:
+        case STATUS_INVALID_OPERATION:
+            return AHARDWAREBUFFER_STATUS_UNSUPPORTED;
+        default:
+            return AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR;
+    }
+}
+
 // ----------------------------------------------------------------------------
 // Public functions
 // ----------------------------------------------------------------------------
@@ -511,6 +527,24 @@
     return AParcel_viewPlatformParcel(parcel)->write(*gb);
 }
 
+ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer) {
+    const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    if (!gb) return ADATASPACE_UNKNOWN;
+    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
+    status_t status = gb->getDataspace(&dataspace);
+    if (status != OK) {
+        return ADATASPACE_UNKNOWN;
+    }
+    return static_cast<ADataSpace>(dataspace);
+}
+
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* buffer,
+                                                        ADataSpace dataspace) {
+    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    auto& mapper = GraphicBufferMapper::get();
+    return filterStatus(mapper.setDataspace(gb->handle, static_cast<ui::Dataspace>(dataspace)));
+}
+
 // ----------------------------------------------------------------------------
 // VNDK functions
 // ----------------------------------------------------------------------------
@@ -552,6 +586,56 @@
     return NO_ERROR;
 }
 
+enum AHardwareBufferStatus AHardwareBuffer_allocate2(
+        const AHardwareBuffer_Desc* desc, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize, AHardwareBuffer** outBuffer) {
+    (void)additionalOptions;
+    (void)additionalOptionsSize;
+    if (!outBuffer || !desc) return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    if (!AHardwareBuffer_isValidDescription(desc, /*log=*/true)) {
+        return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    }
+
+    int format = AHardwareBuffer_convertToPixelFormat(desc->format);
+    uint64_t usage = AHardwareBuffer_convertToGrallocUsageBits(desc->usage);
+
+    std::vector<GraphicBufferAllocator::AdditionalOptions> extras;
+    extras.reserve(additionalOptionsSize);
+    for (size_t i = 0; i < additionalOptionsSize; i++) {
+        extras.push_back(GraphicBufferAllocator::AdditionalOptions{additionalOptions[i].name,
+                                                                   additionalOptions[i].value});
+    }
+
+    const auto extrasCount = extras.size();
+    auto gbuffer = sp<GraphicBuffer>::make(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = true,
+            .width = desc->width,
+            .height = desc->height,
+            .format = format,
+            .layerCount = desc->layers,
+            .usage = usage,
+            .requestorName = std::string("AHardwareBuffer pid [") + std::to_string(getpid()) + "]",
+            .extras = std::move(extras),
+    });
+
+    status_t err = gbuffer->initCheck();
+    if (err != 0 || gbuffer->handle == nullptr) {
+        if (err == NO_MEMORY) {
+        GraphicBuffer::dumpAllocationsToSystemLog();
+        }
+        ALOGE("GraphicBuffer(w=%u, h=%u, lc=%u, extrasCount=%zd) failed (%s), handle=%p",
+              desc->width, desc->height, desc->layers, extrasCount, strerror(-err),
+              gbuffer->handle);
+        return filterStatus(err == 0 ? UNKNOWN_ERROR : err);
+    }
+
+    *outBuffer = AHardwareBuffer_from_GraphicBuffer(gbuffer.get());
+
+    // Ensure the buffer doesn't get destroyed when the sp<> goes away.
+    AHardwareBuffer_acquire(*outBuffer);
+    return AHARDWAREBUFFER_STATUS_OK;
+}
+
 // ----------------------------------------------------------------------------
 // Helpers implementation
 // ----------------------------------------------------------------------------
@@ -652,12 +736,9 @@
     return ahardwarebuffer_format;
 }
 
+// TODO: Remove, this is just to make an overly aggressive ABI checker happy
 int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) {
-    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
-    auto& mapper = GraphicBufferMapper::get();
-    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
-    mapper.getDataspace(gb->handle, &dataspace);
-    return static_cast<int32_t>(dataspace);
+    return ::AHardwareBuffer_getDataSpace(buffer);
 }
 
 uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) {
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index dd5958d..f97eed5 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -152,31 +152,56 @@
 
 int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpace) {
     static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
-    static_assert(static_cast<int>(STANDARD_MASK) == static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
-    static_assert(static_cast<int>(STANDARD_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
-    static_assert(static_cast<int>(STANDARD_BT709) == static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
-    static_assert(static_cast<int>(STANDARD_BT601_625) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
-    static_assert(static_cast<int>(STANDARD_BT601_625_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT601_525) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
-    static_assert(static_cast<int>(STANDARD_BT601_525_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT470M) == static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
-    static_assert(static_cast<int>(STANDARD_FILM) == static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
-    static_assert(static_cast<int>(STANDARD_DCI_P3) == static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
-    static_assert(static_cast<int>(STANDARD_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
-    static_assert(static_cast<int>(TRANSFER_MASK) == static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
-    static_assert(static_cast<int>(TRANSFER_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
-    static_assert(static_cast<int>(TRANSFER_LINEAR) == static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
-    static_assert(static_cast<int>(TRANSFER_SMPTE_170M) == static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_2) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_6) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_8) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
-    static_assert(static_cast<int>(TRANSFER_ST2084) == static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
-    static_assert(static_cast<int>(TRANSFER_HLG) == static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
-    static_assert(static_cast<int>(RANGE_MASK) == static_cast<int>(HAL_DATASPACE_RANGE_MASK));
-    static_assert(static_cast<int>(RANGE_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
-    static_assert(static_cast<int>(RANGE_FULL) == static_cast<int>(HAL_DATASPACE_RANGE_FULL));
-    static_assert(static_cast<int>(RANGE_LIMITED) == static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
-    static_assert(static_cast<int>(RANGE_EXTENDED) == static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT709) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT470M) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_FILM) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_DCI_P3) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_ADOBE_RGB) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_LINEAR) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_SMPTE_170M) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_2) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_6) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_8) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_ST2084) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_HLG) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_MASK));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_FULL) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_FULL));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_LIMITED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_EXTENDED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
     static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
     static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
     static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index 880c694..f145a2f 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -27,8 +27,8 @@
 
 #include <stdint.h>
 
-struct AHardwareBuffer;
-struct AHardwareBuffer_Desc;
+#include <vndk/hardware_buffer.h>
+
 struct ANativeWindowBuffer;
 
 namespace android {
@@ -46,11 +46,6 @@
 // convert HAL format to AHardwareBuffer format (note: this is a no-op)
 uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format);
 
-// retrieves a dataspace from the AHardwareBuffer metadata, if the device
-// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not
-// supported.
-int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer);
-
 // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op)
 uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage);
 
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index eab21fb..d4278be 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -37,7 +37,7 @@
 /**
  * ADataSpace.
  */
-enum ADataSpace {
+enum ADataSpace : int32_t {
     /**
      * Default-assumption data space, when not explicitly specified.
      *
@@ -63,7 +63,7 @@
      * Defines the chromaticity coordinates of the source primaries in terms of
      * the CIE 1931 definition of x and y specified in ISO 11664-1.
      */
-    STANDARD_MASK = 63 << 16,
+    ADATASPACE_STANDARD_MASK = 63 << 16,
 
     /**
      * Chromacity coordinates are unknown or are determined by the application.
@@ -78,7 +78,7 @@
      * For all other formats standard is undefined, and implementations should use
      * an appropriate standard for the data represented.
      */
-    STANDARD_UNSPECIFIED = 0 << 16,
+    ADATASPACE_STANDARD_UNSPECIFIED = 0 << 16,
 
     /**
      * <pre>
@@ -91,7 +91,7 @@
      * Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT709 = 1 << 16,
+    ADATASPACE_STANDARD_BT709 = 1 << 16,
 
     /**
      * <pre>
@@ -106,7 +106,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_625 = 2 << 16,
+    ADATASPACE_STANDARD_BT601_625 = 2 << 16,
 
     /**
      * <pre>
@@ -119,7 +119,7 @@
      * Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT601_625_UNADJUSTED = 3 << 16,
+    ADATASPACE_STANDARD_BT601_625_UNADJUSTED = 3 << 16,
 
     /**
      * <pre>
@@ -134,7 +134,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_525 = 4 << 16,
+    ADATASPACE_STANDARD_BT601_525 = 4 << 16,
 
     /**
      * <pre>
@@ -147,7 +147,7 @@
      * Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation
      * for RGB conversion (as in SMPTE 240M).
      */
-    STANDARD_BT601_525_UNADJUSTED = 5 << 16,
+    ADATASPACE_STANDARD_BT601_525_UNADJUSTED = 5 << 16,
 
     /**
      * <pre>
@@ -160,7 +160,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT2020 = 6 << 16,
+    ADATASPACE_STANDARD_BT2020 = 6 << 16,
 
     /**
      * <pre>
@@ -173,7 +173,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion using the linear domain.
      */
-    STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
+    ADATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
 
     /**
      * <pre>
@@ -186,7 +186,7 @@
      * Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT470M = 8 << 16,
+    ADATASPACE_STANDARD_BT470M = 8 << 16,
 
     /**
      * <pre>
@@ -199,7 +199,7 @@
      * Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_FILM = 9 << 16,
+    ADATASPACE_STANDARD_FILM = 9 << 16,
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3)
@@ -210,7 +210,7 @@
      *  red             0.680   0.320
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_DCI_P3 = 10 << 16,
+    ADATASPACE_STANDARD_DCI_P3 = 10 << 16,
 
     /**
      * Adobe RGB
@@ -221,7 +221,7 @@
      *  red             0.640   0.330
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_ADOBE_RGB = 11 << 16,
+    ADATASPACE_STANDARD_ADOBE_RGB = 11 << 16,
 
     /**
      * Transfer aspect
@@ -236,7 +236,7 @@
      * component. Implementation may apply the transfer function in RGB space
      * for all pixel formats if desired.
      */
-    TRANSFER_MASK = 31 << 22,
+    ADATASPACE_TRANSFER_MASK = 31 << 22,
 
     /**
      * Transfer characteristics are unknown or are determined by the
@@ -244,13 +244,13 @@
      *
      * Implementations should use the following transfer functions:
      *
-     * For YCbCr formats: use TRANSFER_SMPTE_170M
-     * For RGB formats: use TRANSFER_SRGB
+     * For YCbCr formats: use ADATASPACE_TRANSFER_SMPTE_170M
+     * For RGB formats: use ADATASPACE_TRANSFER_SRGB
      *
      * For all other formats transfer function is undefined, and implementations
      * should use an appropriate standard for the data represented.
      */
-    TRANSFER_UNSPECIFIED = 0 << 22,
+    ADATASPACE_TRANSFER_UNSPECIFIED = 0 << 22,
 
     /**
      * Linear transfer.
@@ -260,7 +260,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_LINEAR = 1 << 22,
+    ADATASPACE_TRANSFER_LINEAR = 1 << 22,
 
     /**
      * sRGB transfer.
@@ -271,7 +271,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SRGB = 2 << 22,
+    ADATASPACE_TRANSFER_SRGB = 2 << 22,
 
     /**
      * SMPTE 170M transfer.
@@ -282,7 +282,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SMPTE_170M = 3 << 22,
+    ADATASPACE_TRANSFER_SMPTE_170M = 3 << 22,
 
     /**
      * Display gamma 2.2.
@@ -292,7 +292,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_2 = 4 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_2 = 4 << 22,
 
     /**
      * Display gamma 2.6.
@@ -302,7 +302,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_6 = 5 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_6 = 5 << 22,
 
     /**
      * Display gamma 2.8.
@@ -312,7 +312,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_8 = 6 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_8 = 6 << 22,
 
     /**
      * SMPTE ST 2084 (Dolby Perceptual Quantizer).
@@ -328,7 +328,7 @@
      *         L = 1 corresponds to 10000 cd/m2
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_ST2084 = 7 << 22,
+    ADATASPACE_TRANSFER_ST2084 = 7 << 22,
 
     /**
      * ARIB STD-B67 Hybrid Log Gamma.
@@ -344,7 +344,7 @@
      *          to reference white level of 100 cd/m2
      *      E - corresponding electrical signal</pre>
      */
-    TRANSFER_HLG = 8 << 22,
+    ADATASPACE_TRANSFER_HLG = 8 << 22,
 
     /**
      * Range aspect
@@ -352,7 +352,7 @@
      * Defines the range of values corresponding to the unit range of 0-1.
      * This is defined for YCbCr only, but can be expanded to RGB space.
      */
-    RANGE_MASK = 7 << 27,
+    ADATASPACE_RANGE_MASK = 7 << 27,
 
     /**
      * Range is unknown or are determined by the application.  Implementations
@@ -365,13 +365,13 @@
      * For all other formats range is undefined, and implementations should use
      * an appropriate range for the data represented.
      */
-    RANGE_UNSPECIFIED = 0 << 27,
+    ADATASPACE_RANGE_UNSPECIFIED = 0 << 27,
 
     /**
      * Full range uses all values for Y, Cb and Cr from
      * 0 to 2^b-1, where b is the bit depth of the color format.
      */
-    RANGE_FULL = 1 << 27,
+    ADATASPACE_RANGE_FULL = 1 << 27,
 
     /**
      * Limited range uses values 16/256*2^b to 235/256*2^b for Y, and
@@ -386,7 +386,7 @@
      * Luma (Y) samples should range from 64 to 940, inclusive
      * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive
      */
-    RANGE_LIMITED = 2 << 27,
+    ADATASPACE_RANGE_LIMITED = 2 << 27,
 
     /**
      * Extended range is used for scRGB. Intended for use with
@@ -395,7 +395,7 @@
      * color outside the sRGB gamut.
      * Used to blend / merge multiple dataspaces on a single display.
      */
-    RANGE_EXTENDED = 3 << 27,
+    ADATASPACE_RANGE_EXTENDED = 3 << 27,
 
     /**
      * scRGB linear encoding
@@ -410,7 +410,8 @@
      *
      * Uses extended range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB_LINEAR = 406913024, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_EXTENDED
+    ADATASPACE_SCRGB_LINEAR = 406913024, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                         // ADATASPACE_RANGE_EXTENDED
 
     /**
      * sRGB gamma encoding
@@ -425,7 +426,8 @@
      *
      * Uses full range, sRGB transfer BT.709 standard.
      */
-    ADATASPACE_SRGB = 142671872, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_SRGB = 142671872, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * scRGB
@@ -440,14 +442,16 @@
      *
      * Uses extended range, sRGB transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB = 411107328, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED
+    ADATASPACE_SCRGB = 411107328, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                  // ADATASPACE_RANGE_EXTENDED
 
     /**
      * Display P3
      *
      * Uses full range, sRGB transfer and D65 DCI-P3 standard.
      */
-    ADATASPACE_DISPLAY_P3 = 143261696, // STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_DISPLAY_P3 = 143261696, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -456,7 +460,8 @@
      *
      * Uses full range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
+    ADATASPACE_BT2020_PQ = 163971072, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084 |
+                                      // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -465,7 +470,8 @@
      *
      * Uses limited range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_PQ = 298188800, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084
+                                          // | ADATASPACE_RANGE_LIMITED
 
     /**
      * Adobe RGB
@@ -475,7 +481,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.2 gamma encoding is not supported in HW.
      */
-    ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL
+    ADATASPACE_ADOBE_RGB = 151715840, // ADATASPACE_STANDARD_ADOBE_RGB |
+                                      // ADATASPACE_TRANSFER_GAMMA2_2 | ADATASPACE_RANGE_FULL
 
     /**
      * JPEG File Interchange Format (JFIF)
@@ -484,7 +491,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_JFIF = 146931712, // ADATASPACE_STANDARD_BT601_625 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 625-line
@@ -493,7 +501,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_625 = 281149440, // ADATASPACE_STANDARD_BT601_625 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 525-line
@@ -502,7 +511,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_525 standard.
      */
-    ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_525 = 281280512, // ADATASPACE_STANDARD_BT601_525 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -511,7 +521,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_BT2020 = 147193856, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 709 (BT.709)
@@ -520,7 +531,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.709 standard.
      */
-    ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT709 = 281083904, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                  // ADATASPACE_RANGE_LIMITED
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2
@@ -532,7 +544,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.6 gamma encoding is not supported in HW.
      */
-    ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL
+    ADATASPACE_DCI_P3 = 155844608, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_GAMMA2_6 |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * sRGB linear encoding
@@ -546,21 +559,24 @@
      *
      * Uses full range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
+    ADATASPACE_SRGB_LINEAR = 138477568, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                        // ADATASPACE_RANGE_FULL
 
     /**
      * Hybrid Log Gamma encoding
      *
      * Uses full range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL
+    ADATASPACE_BT2020_HLG = 168165376, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU Hybrid Log Gamma encoding
      *
      * Uses limited range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                           // ADATASPACE_RANGE_LIMITED
 
     /**
      * Depth
@@ -574,7 +590,37 @@
      *
      * Embedded depth metadata following the dynamic depth specification.
      */
-    ADATASPACE_DYNAMIC_DEPTH = 4098
+    ADATASPACE_DYNAMIC_DEPTH = 4098,
+
+#ifndef ADATASPACE_SKIP_LEGACY_DEFINES
+    STANDARD_MASK = ADATASPACE_STANDARD_MASK,
+    STANDARD_UNSPECIFIED = ADATASPACE_STANDARD_UNSPECIFIED,
+    STANDARD_BT709 = ADATASPACE_STANDARD_BT709,
+    STANDARD_BT601_625 = ADATASPACE_STANDARD_BT601_625,
+    STANDARD_BT601_625_UNADJUSTED = ADATASPACE_STANDARD_BT601_625_UNADJUSTED,
+    STANDARD_BT601_525 = ADATASPACE_STANDARD_BT601_525,
+    STANDARD_BT601_525_UNADJUSTED = ADATASPACE_STANDARD_BT601_525_UNADJUSTED,
+    STANDARD_BT470M = ADATASPACE_STANDARD_BT470M,
+    STANDARD_BT2020 = ADATASPACE_STANDARD_BT2020,
+    STANDARD_FILM = ADATASPACE_STANDARD_FILM,
+    STANDARD_DCI_P3 = ADATASPACE_STANDARD_DCI_P3,
+    STANDARD_ADOBE_RGB = ADATASPACE_STANDARD_ADOBE_RGB,
+    TRANSFER_MASK = ADATASPACE_TRANSFER_MASK,
+    TRANSFER_UNSPECIFIED = ADATASPACE_TRANSFER_UNSPECIFIED,
+    TRANSFER_LINEAR = ADATASPACE_TRANSFER_LINEAR,
+    TRANSFER_SMPTE_170M = ADATASPACE_TRANSFER_SMPTE_170M,
+    TRANSFER_GAMMA2_2 = ADATASPACE_TRANSFER_GAMMA2_2,
+    TRANSFER_GAMMA2_6 = ADATASPACE_TRANSFER_GAMMA2_6,
+    TRANSFER_GAMMA2_8 = ADATASPACE_TRANSFER_GAMMA2_8,
+    TRANSFER_SRGB = ADATASPACE_TRANSFER_SRGB,
+    TRANSFER_ST2084 = ADATASPACE_TRANSFER_ST2084,
+    TRANSFER_HLG = ADATASPACE_TRANSFER_HLG,
+    RANGE_MASK = ADATASPACE_RANGE_MASK,
+    RANGE_UNSPECIFIED = ADATASPACE_RANGE_UNSPECIFIED,
+    RANGE_FULL = ADATASPACE_RANGE_FULL,
+    RANGE_LIMITED = ADATASPACE_RANGE_LIMITED,
+    RANGE_EXTENDED = ADATASPACE_RANGE_EXTENDED,
+#endif
 };
 
 __END_DECLS
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 21798d0..e0e30c3 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -46,6 +46,9 @@
 #define ANDROID_HARDWARE_BUFFER_H
 
 #include <android/rect.h>
+#define ADATASPACE_SKIP_LEGACY_DEFINES
+#include <android/data_space.h>
+#undef ADATASPACE_SKIP_LEGACY_DEFINES
 #include <inttypes.h>
 #include <sys/cdefs.h>
 
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
index a252245..68ac7e0 100644
--- a/libs/nativewindow/include/android/native_window_aidl.h
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -34,6 +34,12 @@
 #include <android/native_window.h>
 #include <sys/cdefs.h>
 
+// Only required by the AIDL glue helper
+#ifdef __cplusplus
+#include <sstream>
+#include <string>
+#endif // __cplusplus
+
 __BEGIN_DECLS
 
 /**
@@ -80,7 +86,7 @@
  * Takes ownership of the ANativeWindow* given to it in reset() and will automatically
  * destroy it in the destructor, similar to a smart pointer container
  */
-class NativeWindow {
+class NativeWindow final {
 public:
     NativeWindow() noexcept {}
     explicit NativeWindow(ANativeWindow* _Nullable window) {
@@ -97,14 +103,22 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        return ANativeWindow_readFromParcel(parcel, &mWindow);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return ANativeWindow_readFromParcel(parcel, &mWindow);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
         if (!mWindow) {
             return STATUS_BAD_VALUE;
         }
-        return ANativeWindow_writeToParcel(mWindow, parcel);
+        if (__builtin_available(android __ANDROID_API_U__, *)) {
+            return ANativeWindow_writeToParcel(mWindow, parcel);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
     }
 
     /**
@@ -123,15 +137,29 @@
         }
         mWindow = window;
     }
-    inline ANativeWindow* _Nullable operator-> () const { return mWindow;  }
+
     inline ANativeWindow* _Nullable get() const { return mWindow; }
-    inline explicit operator bool () const { return mWindow != nullptr; }
 
     NativeWindow& operator=(NativeWindow&& other) noexcept {
         mWindow = other.release(); // steal ownership from r-value
         return *this;
     }
 
+    inline ANativeWindow* _Nullable operator->() const { return mWindow; }
+    inline explicit operator bool() const { return mWindow != nullptr; }
+    inline bool operator==(const NativeWindow& rhs) const { return mWindow == rhs.mWindow; }
+    inline bool operator!=(const NativeWindow& rhs) const { return !(*this == rhs); }
+    inline bool operator<(const NativeWindow& rhs) const { return mWindow < rhs.mWindow; }
+    inline bool operator>(const NativeWindow& rhs) const { return rhs < *this; }
+    inline bool operator>=(const NativeWindow& rhs) const { return !(*this < rhs); }
+    inline bool operator<=(const NativeWindow& rhs) const { return !(*this > rhs); }
+
+    std::string toString() const {
+        std::ostringstream ss;
+        ss << "NativeWindow: " << mWindow;
+        return ss.str();
+    }
+
     /**
      * Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
      * is released.
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index b068f48..969a5cf 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
 };
 
 /*
@@ -1090,10 +1095,19 @@
     ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
 
     /**
+     * Indicates that, as a result of a user interaction, an animation is likely to start.
+     * This category is a signal that a user interaction heuristic determined the need of a
+     * high refresh rate, and is not an explicit request from the app.
+     * As opposed to FRAME_RATE_CATEGORY_HIGH, this vote may be ignored in favor of
+     * more explicit votes.
+     */
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT = 4,
+
+    /**
      * Indicates a frame rate suitable for animations that require a high frame rate, which may
      * increase smoothness but may also increase power usage.
      */
-    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+    ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 5
 };
 
 /*
@@ -1103,17 +1117,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/nativewindow/include/vndk/hardware_buffer.h b/libs/nativewindow/include/vndk/hardware_buffer.h
index 21931bb..4918601 100644
--- a/libs/nativewindow/include/vndk/hardware_buffer.h
+++ b/libs/nativewindow/include/vndk/hardware_buffer.h
@@ -21,6 +21,7 @@
 #include <android/hardware_buffer.h>
 
 #include <cutils/native_handle.h>
+#include <errno.h>
 
 __BEGIN_DECLS
 
@@ -105,6 +106,76 @@
     AHARDWAREBUFFER_USAGE_CAMERA_MASK               = 6UL << 16,
 };
 
+/**
+ * Additional options for AHardwareBuffer_allocate2. These correspond to
+ * android.hardware.graphics.common.ExtendableType
+ */
+typedef struct {
+    const char* _Nonnull name;
+    int64_t value;
+} AHardwareBufferLongOptions;
+
+enum AHardwareBufferStatus : int32_t {
+    /* Success, no error */
+    AHARDWAREBUFFER_STATUS_OK = 0,
+    /* There's insufficient memory to satisfy the request */
+    AHARDWAREBUFFER_STATUS_NO_MEMORY = -ENOMEM,
+    /* The given argument is invalid */
+    AHARDWAREBUFFER_STATUS_BAD_VALUE = -EINVAL,
+    /* The requested operation is not supported by the device */
+    AHARDWAREBUFFER_STATUS_UNSUPPORTED = -ENOSYS,
+    /* An unknown error occurred */
+    AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR = (-2147483647 - 1),
+};
+
+/**
+ * Allocates a buffer that matches the passed AHardwareBuffer_Desc with additional options
+ *
+ * If allocation succeeds, the buffer can be used according to the
+ * usage flags specified in its description. If a buffer is used in ways
+ * not compatible with its usage flags, the results are undefined and
+ * may include program termination.
+ *
+ * @param desc The AHardwareBuffer_Desc that describes the allocation to request. Note that `stride`
+ *             is ignored.
+ * @param additionalOptions A pointer to an array of AHardwareBufferLongOptions with additional
+ *                          string key + long value options that may be specified. May be null if
+ *                          `additionalOptionsSize` is 0
+ * @param additionalOptionsSize The number of additional options to pass
+ * @param outBuffer The resulting buffer allocation
+ * @return AHARDWAREBUFFER_STATUS_OK on success
+ *         AHARDWAREBUFFER_STATUS_NO_MEMORY if there's insufficient resources for the allocation
+ *         AHARDWAREBUFFER_STATUS_BAD_VALUE if the provided description & options are not supported
+ *         by the device
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other error
+ * any reason. The returned buffer has a reference count of 1.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_allocate2(
+        const AHardwareBuffer_Desc* _Nonnull desc,
+        const AHardwareBufferLongOptions* _Nullable additionalOptions, size_t additionalOptionsSize,
+        AHardwareBuffer* _Nullable* _Nonnull outBuffer) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Queries the dataspace of the given AHardwareBuffer.
+ *
+ * @param buffer The non-null buffer for which to query the Dataspace
+ * @return The dataspace of the buffer, or ADATASPACE_UNKNOWN if one hasn't been set
+ */
+enum ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the dataspace of the given AHardwareBuffer
+ * @param buffer The non-null buffer for which to set the dataspace
+ * @param dataSpace The dataspace to set
+ * @return AHARDWAREBUFFER_STATUS_OK on success,
+ *         AHARDWAREBUFFER_STATUS_UNSUPPORTED if the device doesn't support setting the dataspace,
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other failure.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* _Nonnull buffer,
+                                                        enum ADataSpace dataSpace)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
 
 #endif /* ANDROID_VNDK_NATIVEWINDOW_AHARDWAREBUFFER_H */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index dcb5068..8bc1292 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -2,10 +2,11 @@
   global:
     AHardwareBuffer_acquire;
     AHardwareBuffer_allocate;
-    AHardwareBuffer_createFromHandle; # llndk # systemapi
+    AHardwareBuffer_allocate2; # llndk systemapi
+    AHardwareBuffer_createFromHandle; # llndk systemapi
     AHardwareBuffer_describe;
     AHardwareBuffer_getId; # introduced=31
-    AHardwareBuffer_getNativeHandle; # llndk # systemapi
+    AHardwareBuffer_getNativeHandle; # llndk systemapi
     AHardwareBuffer_isSupported; # introduced=29
     AHardwareBuffer_lock;
     AHardwareBuffer_lockAndGetInfo; # introduced=29
@@ -16,6 +17,8 @@
     AHardwareBuffer_unlock;
     AHardwareBuffer_readFromParcel; # introduced=34
     AHardwareBuffer_writeToParcel; # introduced=34
+    AHardwareBuffer_getDataSpace; # llndk systemapi
+    AHardwareBuffer_setDataSpace; # llndk systemapi
     ANativeWindowBuffer_getHardwareBuffer; # llndk
     ANativeWindow_OemStorageGet; # llndk
     ANativeWindow_OemStorageSet; # llndk
@@ -26,18 +29,18 @@
     ANativeWindow_getBuffersDefaultDataSpace; # introduced=34
     ANativeWindow_getFormat;
     ANativeWindow_getHeight;
-    ANativeWindow_getLastDequeueDuration; # systemapi # introduced=30
-    ANativeWindow_getLastDequeueStartTime; # systemapi # introduced=30
-    ANativeWindow_getLastQueueDuration; # systemapi # introduced=30
+    ANativeWindow_getLastDequeueDuration; # systemapi introduced=30
+    ANativeWindow_getLastDequeueStartTime; # systemapi introduced=30
+    ANativeWindow_getLastQueueDuration; # systemapi introduced=30
     ANativeWindow_getWidth;
     ANativeWindow_lock;
     ANativeWindow_query; # llndk
     ANativeWindow_queryf; # llndk
     ANativeWindow_queueBuffer; # llndk
-    ANativeWindow_setCancelBufferInterceptor; # systemapi # introduced=30
-    ANativeWindow_setDequeueBufferInterceptor; # systemapi # introduced=30
-    ANativeWindow_setPerformInterceptor; # systemapi # introduced=30
-    ANativeWindow_setQueueBufferInterceptor; # systemapi # introduced=30
+    ANativeWindow_setCancelBufferInterceptor; # systemapi introduced=30
+    ANativeWindow_setDequeueBufferInterceptor; # systemapi introduced=30
+    ANativeWindow_setPerformInterceptor; # systemapi introduced=30
+    ANativeWindow_setQueueBufferInterceptor; # systemapi introduced=30
     ANativeWindow_release;
     ANativeWindow_setAutoPrerotation; # llndk
     ANativeWindow_setAutoRefresh; # llndk
@@ -48,7 +51,7 @@
     ANativeWindow_setBuffersGeometry;
     ANativeWindow_setBuffersTimestamp; # llndk
     ANativeWindow_setBuffersTransform;
-    ANativeWindow_setDequeueTimeout; # systemapi # introduced=30
+    ANativeWindow_setDequeueTimeout; # systemapi introduced=30
     ANativeWindow_setFrameRate; # introduced=30
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
diff --git a/libs/nativewindow/tests/AHardwareBufferTest.cpp b/libs/nativewindow/tests/AHardwareBufferTest.cpp
index ef863b6..1f0128a 100644
--- a/libs/nativewindow/tests/AHardwareBufferTest.cpp
+++ b/libs/nativewindow/tests/AHardwareBufferTest.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "AHardwareBuffer_test"
 //#define LOG_NDEBUG 0
 
+#include <android-base/properties.h>
+#include <android/data_space.h>
 #include <android/hardware/graphics/common/1.0/types.h>
 #include <gtest/gtest.h>
 #include <private/android/AHardwareBufferHelpers.h>
@@ -26,6 +28,10 @@
 using namespace android;
 using android::hardware::graphics::common::V1_0::BufferUsage;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 static ::testing::AssertionResult BuildHexFailureMessage(uint64_t expected,
         uint64_t actual, const char* type) {
     std::ostringstream ss;
@@ -170,3 +176,83 @@
 
     EXPECT_NE(id1, id2);
 }
+
+TEST(AHardwareBufferTest, Allocate2NoExtras) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 1,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_BLOB,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocate2(&desc, nullptr, 0, &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, Allocate2WithExtras) {
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Unknown gralloc HAL, cannot test extras";
+    }
+
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+    ASSERT_EQ(0, AHardwareBuffer_allocate2(&desc, extras.data(), extras.size(), &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, GetSetDataspace) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocate(&desc, &buffer));
+
+    EXPECT_EQ(ADATASPACE_UNKNOWN, AHardwareBuffer_getDataSpace(buffer));
+    AHardwareBufferStatus status = AHardwareBuffer_setDataSpace(buffer, ADATASPACE_DISPLAY_P3);
+    if (status != AHARDWAREBUFFER_STATUS_UNSUPPORTED) {
+        EXPECT_EQ(0, status);
+        EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+    }
+
+    AHardwareBuffer_release(buffer);
+}
\ No newline at end of file
diff --git a/libs/nativewindow/tests/Android.bp b/libs/nativewindow/tests/Android.bp
index 30737c1..d7c7eb3 100644
--- a/libs/nativewindow/tests/Android.bp
+++ b/libs/nativewindow/tests/Android.bp
@@ -31,6 +31,7 @@
         "device-tests",
     ],
     shared_libs: [
+        "libbase",
         "libgui",
         "liblog",
         "libnativewindow",
@@ -44,5 +45,8 @@
         "ANativeWindowTest.cpp",
         "c_compatibility.c",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
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/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 2053c6a..d688b51 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -236,7 +236,13 @@
             err = selectEGLConfig(display, format, 0, &config);
             if (err != NO_ERROR) {
                 // this EGL is too lame for android
-                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
+                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up"
+                                 " (format: %d, vendor: %s, version: %s, extensions: %s, Client"
+                                 " API: %s)",
+                                 format, eglQueryString(display, EGL_VENDOR),
+                                 eglQueryString(display, EGL_VERSION),
+                                 eglQueryString(display, EGL_EXTENSIONS),
+                                 eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
             }
         }
     }
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/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 367bee8..f58f543 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -125,8 +125,10 @@
 }
 
 void RenderEngineThreaded::waitUntilInitialized() const {
-    std::unique_lock<std::mutex> lock(mInitializedMutex);
-    mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
+    if (!mIsInitialized) {
+        std::unique_lock<std::mutex> lock(mInitializedMutex);
+        mInitializedCondition.wait(lock, [this] { return mIsInitialized.load(); });
+    }
 }
 
 std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 74af2bd..3f1e67f 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -97,7 +97,7 @@
 
     // Used to allow select thread safe methods to be accessed without requiring the
     // method to be invoked on the RenderEngine thread
-    bool mIsInitialized = false;
+    std::atomic_bool mIsInitialized = false;
     mutable std::mutex mInitializedMutex;
     mutable std::condition_variable mInitializedCondition;
 
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index d112a12..f8ee3fc 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -248,6 +248,22 @@
     return static_cast<ssize_t>(mSensors.size());
 }
 
+ssize_t SensorManager::getDefaultDeviceSensorList(Vector<Sensor> & list) {
+    Mutex::Autolock _l(mLock);
+    status_t err = assertStateLocked();
+    if (err < 0) {
+        return static_cast<ssize_t>(err);
+    }
+
+    if (mDeviceId == DEVICE_ID_DEFAULT) {
+        list = mSensors;
+    } else {
+        list = mSensorServer->getSensorList(mOpPackageName);
+    }
+
+    return static_cast<ssize_t>(list.size());
+}
+
 ssize_t SensorManager::getDynamicSensorList(Vector<Sensor> & dynamicSensors) {
     Mutex::Autolock _l(mLock);
     status_t err = assertStateLocked();
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index e67fac7..49f050a 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -58,6 +58,7 @@
     ~SensorManager();
 
     ssize_t getSensorList(Sensor const* const** list);
+    ssize_t getDefaultDeviceSensorList(Vector<Sensor> & list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
     ssize_t getDynamicSensorList(Sensor const* const** list);
     ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
diff --git a/libs/ui/FenceTime.cpp b/libs/ui/FenceTime.cpp
index 538c1d2..4246c40 100644
--- a/libs/ui/FenceTime.cpp
+++ b/libs/ui/FenceTime.cpp
@@ -363,9 +363,9 @@
 }
 
 void FenceToFenceTimeMap::garbageCollectLocked() {
-    for (auto& it : mMap) {
+    for (auto it = mMap.begin(); it != mMap.end();) {
         // Erase all expired weak pointers from the vector.
-        auto& vect = it.second;
+        auto& vect = it->second;
         vect.erase(
                 std::remove_if(vect.begin(), vect.end(),
                         [](const std::weak_ptr<FenceTime>& ft) {
@@ -375,7 +375,9 @@
 
         // Also erase the map entry if the vector is now empty.
         if (vect.empty()) {
-            mMap.erase(it.first);
+            it = mMap.erase(it);
+        } else {
+            it++;
         }
     }
 }
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index e9b5dec..a5aca99 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -384,8 +384,8 @@
 
 status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                     uint32_t bufferCount, uint32_t* outStride,
-                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+                                     uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                                     bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo = {};
     descriptorInfo.width = width;
     descriptorInfo.height = height;
@@ -400,6 +400,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 474d381..152b35a 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -371,7 +371,7 @@
 
 status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
@@ -383,6 +383,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index b6274ab..d6970e0 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;
@@ -1069,7 +1069,7 @@
 
 status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
@@ -1084,6 +1084,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     if (mAidlAllocator) {
         AllocationResult result;
 #pragma clang diagnostic push
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 37ebfc4..b07e155 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -19,6 +19,7 @@
 
 #include <ui/Gralloc5.h>
 
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
@@ -215,55 +216,75 @@
 
 status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t *outStride,
-                                     buffer_handle_t *outBufferHandles, bool importBuffers) const {
-    auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage);
+                                     uint64_t usage, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+    auto result = allocate(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = importBuffers,
+            .width = width,
+            .height = height,
+            .format = format,
+            .layerCount = layerCount,
+            .usage = usage,
+            .requestorName = requestorName,
+    });
+
+    *outStride = result.stride;
+    outBufferHandles[0] = result.handle;
+    return result.status;
+}
+
+GraphicBufferAllocator::AllocationResult Gralloc5Allocator::allocate(
+        const GraphicBufferAllocator::AllocationRequest& request) const {
+    auto descriptorInfo = makeDescriptor(request.requestorName, request.width, request.height,
+                                         request.format, request.layerCount, request.usage);
     if (!descriptorInfo) {
-        return BAD_VALUE;
+        return GraphicBufferAllocator::AllocationResult{BAD_VALUE};
+    }
+
+    descriptorInfo->additionalOptions.reserve(request.extras.size());
+    for (const auto& option : request.extras) {
+        ExtendableType type;
+        type.name = option.name;
+        type.value = option.value;
+        descriptorInfo->additionalOptions.push_back(std::move(type));
     }
 
     AllocationResult result;
-    auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result);
+    auto status = mAllocator->allocate2(*descriptorInfo, 1, &result);
     if (!status.isOk()) {
         auto error = status.getExceptionCode();
         if (error == EX_SERVICE_SPECIFIC) {
-            error = status.getServiceSpecificError();
+            switch (static_cast<AllocationError>(status.getServiceSpecificError())) {
+                case AllocationError::BAD_DESCRIPTOR:
+                    error = BAD_VALUE;
+                    break;
+                case AllocationError::NO_RESOURCES:
+                    error = NO_MEMORY;
+                    break;
+                default:
+                    error = UNKNOWN_ERROR;
+                    break;
+            }
         }
-        if (error == OK) {
-            error = UNKNOWN_ERROR;
-        }
-        return error;
+        return GraphicBufferAllocator::AllocationResult{error};
     }
 
-    if (importBuffers) {
-        for (uint32_t i = 0; i < bufferCount; i++) {
-            auto handle = makeFromAidl(result.buffers[i]);
-            auto error = mMapper.importBuffer(handle, &outBufferHandles[i]);
-            native_handle_delete(handle);
-            if (error != NO_ERROR) {
-                for (uint32_t j = 0; j < i; j++) {
-                    mMapper.freeBuffer(outBufferHandles[j]);
-                    outBufferHandles[j] = nullptr;
-                }
-                return error;
-            }
+    GraphicBufferAllocator::AllocationResult ret{OK};
+    if (request.importBuffer) {
+        auto handle = makeFromAidl(result.buffers[0]);
+        auto error = mMapper.importBuffer(handle, &ret.handle);
+        native_handle_delete(handle);
+        if (error != NO_ERROR) {
+            return GraphicBufferAllocator::AllocationResult{error};
         }
     } else {
-        for (uint32_t i = 0; i < bufferCount; i++) {
-            outBufferHandles[i] = dupFromAidl(result.buffers[i]);
-            if (!outBufferHandles[i]) {
-                for (uint32_t j = 0; j < i; j++) {
-                    auto buffer = const_cast<native_handle_t *>(outBufferHandles[j]);
-                    native_handle_close(buffer);
-                    native_handle_delete(buffer);
-                    outBufferHandles[j] = nullptr;
-                }
-                return NO_MEMORY;
-            }
+        ret.handle = dupFromAidl(result.buffers[0]);
+        if (!ret.handle) {
+            return GraphicBufferAllocator::AllocationResult{NO_MEMORY};
         }
     }
 
-    *outStride = result.stride;
+    ret.stride = result.stride;
 
     // Release all the resources held by AllocationResult (specifically any remaining FDs)
     result = {};
@@ -272,7 +293,7 @@
     // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct)
     // IPCThreadState::self()->flushCommands();
 
-    return OK;
+    return ret;
 }
 
 void Gralloc5Mapper::preload() {
@@ -518,14 +539,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/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 429760f..c007fdb 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -106,6 +106,26 @@
                                 inUsage, inStride);
 }
 
+GraphicBuffer::GraphicBuffer(const GraphicBufferAllocator::AllocationRequest& request)
+      : GraphicBuffer() {
+    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
+    auto result = allocator.allocate(request);
+    mInitCheck = result.status;
+    if (result.status == NO_ERROR) {
+        handle = result.handle;
+        stride = result.stride;
+
+        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
+
+        width = static_cast<int>(request.width);
+        height = static_cast<int>(request.height);
+        format = request.format;
+        layerCount = request.layerCount;
+        usage = request.usage;
+        usage_deprecated = int(usage);
+    }
+}
+
 GraphicBuffer::~GraphicBuffer()
 {
     ATRACE_CALL();
@@ -143,6 +163,10 @@
             const_cast<GraphicBuffer*>(this));
 }
 
+status_t GraphicBuffer::getDataspace(ui::Dataspace* outDataspace) const {
+    return mBufferMapper.getDataspace(handle, outDataspace);
+}
+
 status_t GraphicBuffer::reallocate(uint32_t inWidth, uint32_t inHeight,
         PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage)
 {
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index eb0bd4e..98082fb 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -113,6 +113,79 @@
     ALOGD("%s", s.c_str());
 }
 
+auto GraphicBufferAllocator::allocate(const AllocationRequest& request) -> AllocationResult {
+    ATRACE_CALL();
+    if (!request.width || !request.height) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    const auto width = request.width;
+    const auto height = request.height;
+
+    const uint32_t bpp = bytesPerPixel(request.format);
+    if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": Requesting too large a buffer size",
+              request.width, request.height, request.layerCount, request.format, request.usage);
+        return AllocationResult(BAD_VALUE);
+    }
+
+    if (request.layerCount < 1) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    auto result = mAllocator->allocate(request);
+    if (result.status == UNKNOWN_TRANSACTION) {
+        if (!request.extras.empty()) {
+            ALOGE("Failed to allocate with additional options, allocator version mis-match? "
+                  "gralloc version = %d",
+                  (int)mMapper.getMapperVersion());
+            return result;
+        }
+        // If there's no additional options, fall back to previous allocate version
+        result.status = mAllocator->allocate(request.requestorName, request.width, request.height,
+                                             request.format, request.layerCount, request.usage,
+                                             &result.stride, &result.handle, request.importBuffer);
+    }
+
+    if (result.status != NO_ERROR) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": %d",
+              request.width, request.height, request.layerCount, request.format, request.usage,
+              result.status);
+        return result;
+    }
+
+    if (!request.importBuffer) {
+        return result;
+    }
+    size_t bufSize;
+
+    // if stride has no meaning or is too large,
+    // approximate size with the input width instead
+    if ((result.stride) != 0 &&
+        std::numeric_limits<size_t>::max() / height / (result.stride) < static_cast<size_t>(bpp)) {
+        bufSize = static_cast<size_t>(width) * height * bpp;
+    } else {
+        bufSize = static_cast<size_t>((result.stride)) * height * bpp;
+    }
+
+    Mutex::Autolock _l(sLock);
+    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
+    alloc_rec_t rec;
+    rec.width = width;
+    rec.height = height;
+    rec.stride = result.stride;
+    rec.format = request.format;
+    rec.layerCount = request.layerCount;
+    rec.usage = request.usage;
+    rec.size = bufSize;
+    rec.requestorName = request.requestorName;
+    list.add(result.handle, rec);
+
+    return result;
+}
+
 status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
                                                 uint32_t layerCount, uint64_t usage,
                                                 buffer_handle_t* handle, uint32_t* stride,
@@ -141,7 +214,7 @@
     usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));
 
     status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
-                                          1, stride, handle, importBuffer);
+                                          stride, handle, importBuffer);
     if (error != NO_ERROR) {
         ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
               "usage %" PRIx64 ": %d",
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/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 496ba57..e6015e0 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -23,6 +23,7 @@
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/StrongPointer.h>
+#include "GraphicBufferAllocator.h"
 
 #include <string>
 
@@ -218,9 +219,13 @@
      */
     virtual status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                               PixelFormat format, uint32_t layerCount, uint64_t usage,
-                              uint32_t bufferCount, uint32_t* outStride,
-                              buffer_handle_t* outBufferHandles,
+                              uint32_t* outStride, buffer_handle_t* outBufferHandles,
                               bool importBuffers = true) const = 0;
+
+    virtual GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const {
+        return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
+    }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index a7b6f492..e50bb3a 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -81,9 +81,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc2Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 7367549..035684a 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -82,9 +82,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc3Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index df43be8..0f469c0 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -174,9 +174,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc4Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index 44b97d1..f9e8f5e 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -172,10 +172,12 @@
 
     [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                                     PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                    uint32_t bufferCount, uint32_t *outStride,
-                                    buffer_handle_t *outBufferHandles,
+                                    uint32_t* outStride, buffer_handle_t* outBufferHandles,
                                     bool importBuffers) const override;
 
+    [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const override;
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index f859848..652d8ba 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -26,6 +26,7 @@
 
 #include <android/hardware_buffer.h>
 #include <ui/ANativeObjectBase.h>
+#include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
@@ -103,6 +104,8 @@
             uint32_t inLayerCount, uint64_t inUsage,
             std::string requestorName = "<Unknown>");
 
+    GraphicBuffer(const GraphicBufferAllocator::AllocationRequest&);
+
     // Create a GraphicBuffer from an existing handle.
     enum HandleWrapMethod : uint8_t {
         // Wrap and use the handle directly.  It assumes the handle has been
@@ -169,6 +172,8 @@
         mGenerationNumber = generation;
     }
 
+    status_t getDataspace(ui::Dataspace* outDataspace) const;
+
     // This function is privileged.  It requires access to the allocator
     // device or service, which usually involves adding suitable selinux
     // rules.
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 3ed988c..8f461e1 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -22,6 +22,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <cutils/native_handle.h>
 
@@ -42,6 +43,35 @@
 public:
     static inline GraphicBufferAllocator& get() { return getInstance(); }
 
+    struct AdditionalOptions {
+        const char* name;
+        int64_t value;
+    };
+
+    struct AllocationRequest {
+        bool importBuffer;
+        uint32_t width;
+        uint32_t height;
+        PixelFormat format;
+        uint32_t layerCount;
+        uint64_t usage;
+        std::string requestorName;
+        std::vector<AdditionalOptions> extras;
+    };
+
+    struct AllocationResult {
+        status_t status;
+        buffer_handle_t handle = nullptr;
+        uint32_t stride = 0;
+
+        explicit AllocationResult(status_t status) : status(status) {}
+
+        explicit AllocationResult(buffer_handle_t handle, uint32_t stride)
+              : status(OK), handle(handle), stride(stride) {}
+    };
+
+    AllocationResult allocate(const AllocationRequest&);
+
     /**
      * Allocates and imports a gralloc buffer.
      *
diff --git a/libs/ui/tests/GraphicBufferAllocator_test.cpp b/libs/ui/tests/GraphicBufferAllocator_test.cpp
index f4c0afa..efca083 100644
--- a/libs/ui/tests/GraphicBufferAllocator_test.cpp
+++ b/libs/ui/tests/GraphicBufferAllocator_test.cpp
@@ -51,7 +51,7 @@
         std::cout << "Setting expected stride to " << stride << std::endl;
         EXPECT_CALL(*(reinterpret_cast<const mock::MockGrallocAllocator*>(mAllocator.get())),
                     allocate)
-                .WillOnce(DoAll(SetArgPointee<7>(stride), Return(err)));
+                .WillOnce(DoAll(SetArgPointee<6>(stride), Return(err)));
     }
     std::unique_ptr<const GrallocAllocator>& getAllocator() { return mAllocator; }
 };
diff --git a/libs/ui/tests/mock/MockGrallocAllocator.h b/libs/ui/tests/mock/MockGrallocAllocator.h
index d62e3e2..d02b387 100644
--- a/libs/ui/tests/mock/MockGrallocAllocator.h
+++ b/libs/ui/tests/mock/MockGrallocAllocator.h
@@ -35,7 +35,7 @@
     MOCK_METHOD(std::string, dumpDebugInfo, (bool less), (const, override));
     MOCK_METHOD(status_t, allocate,
                 (std::string requestorName, uint32_t width, uint32_t height, PixelFormat format,
-                 uint32_t layerCount, uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                 uint32_t layerCount, uint64_t usage, uint32_t* outStride,
                  buffer_handle_t* outBufferHandles, bool less),
                 (const, override));
 };
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/opengl/Android.bp b/opengl/Android.bp
index b15694b..4454f36 100644
--- a/opengl/Android.bp
+++ b/opengl/Android.bp
@@ -72,6 +72,10 @@
     llndk: {
         llndk_headers: true,
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 subdirs = [
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 04e2fff..e487cbc 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -66,6 +66,8 @@
 static const char* PERSIST_DRIVER_SUFFIX_PROPERTY = "persist.graphics.egl";
 static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
 static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
+static const char* ANGLE_SUFFIX_VALUE = "angle";
+static const char* VENDOR_ANGLE_BUILD = "ro.gfx.angle.supported";
 
 static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
         PERSIST_DRIVER_SUFFIX_PROPERTY,
@@ -80,6 +82,13 @@
         "/vendor/lib/egl";
 #endif
 
+static const char* const SYSTEM_LIB_DIR =
+#if defined(__LP64__)
+        "/system/lib64";
+#else
+        "/system/lib";
+#endif
+
 static void* do_dlopen(const char* path, int mode) {
     ATRACE_CALL();
     return dlopen(path, mode);
@@ -434,98 +443,110 @@
     }
 }
 
+static std::string findLibrary(const std::string libraryName, const std::string searchPath,
+                               const bool exact) {
+    if (exact) {
+        std::string absolutePath = searchPath + "/" + libraryName + ".so";
+        if (!access(absolutePath.c_str(), R_OK)) {
+            return absolutePath;
+        }
+        return std::string();
+    }
+
+    DIR* d = opendir(searchPath.c_str());
+    if (d != nullptr) {
+        struct dirent* e;
+        while ((e = readdir(d)) != nullptr) {
+            if (e->d_type == DT_DIR) {
+                continue;
+            }
+            if (!strcmp(e->d_name, "libGLES_android.so")) {
+                // always skip the software renderer
+                continue;
+            }
+            if (strstr(e->d_name, libraryName.c_str()) == e->d_name) {
+                if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
+                    std::string result = searchPath + "/" + e->d_name;
+                    closedir(d);
+                    return result;
+                }
+            }
+        }
+        closedir(d);
+    }
+    // Driver not found. gah.
+    return std::string();
+}
+
 static void* load_system_driver(const char* kind, const char* suffix, const bool exact) {
     ATRACE_CALL();
-    class MatchFile {
-    public:
-        static std::string find(const char* libraryName, const bool exact) {
-            std::string absolutePath;
-            if (findLibPath(absolutePath, libraryName, exact)) {
-                return absolutePath;
-            }
-
-            // Driver not found. gah.
-            return std::string();
-        }
-    private:
-        static bool findLibPath(std::string& result, const std::string& pattern, bool exact) {
-            const std::string vendorLibEglDirString = std::string(VENDOR_LIB_EGL_DIR);
-            if (exact) {
-                std::string absolutePath = vendorLibEglDirString + "/" + pattern + ".so";
-                if (!access(absolutePath.c_str(), R_OK)) {
-                    result = absolutePath;
-                    return true;
-                }
-                return false;
-            }
-
-            DIR* d = opendir(VENDOR_LIB_EGL_DIR);
-            if (d != nullptr) {
-                struct dirent* e;
-                while ((e = readdir(d)) != nullptr) {
-                    if (e->d_type == DT_DIR) {
-                        continue;
-                    }
-                    if (!strcmp(e->d_name, "libGLES_android.so")) {
-                        // always skip the software renderer
-                        continue;
-                    }
-                    if (strstr(e->d_name, pattern.c_str()) == e->d_name) {
-                        if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
-                            result = vendorLibEglDirString + "/" + e->d_name;
-                            closedir(d);
-                            return true;
-                        }
-                    }
-                }
-                closedir(d);
-            }
-            return false;
-        }
-    };
 
     std::string libraryName = std::string("lib") + kind;
     if (suffix) {
         libraryName += std::string("_") + suffix;
     } else if (!exact) {
-        // Deprecated: we look for files that match
-        //      libGLES_*.so, or:
+        // Deprecated for devices launching in Android 14
+        // Look for files that match
+        //      libGLES_*.so, or,
         //      libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so
         libraryName += std::string("_");
     }
-    std::string absolutePath = MatchFile::find(libraryName.c_str(), exact);
+
+    void* dso = nullptr;
+
+    const bool AngleInVendor = property_get_bool(VENDOR_ANGLE_BUILD, false);
+    const bool isSuffixAngle = suffix != nullptr && strcmp(suffix, ANGLE_SUFFIX_VALUE) == 0;
+    // Only use sphal namespace when system ANGLE binaries are not the default drivers.
+    const bool useSphalNamespace =  !isSuffixAngle || AngleInVendor;
+
+    const std::string absolutePath =
+            findLibrary(libraryName, useSphalNamespace ? VENDOR_LIB_EGL_DIR : SYSTEM_LIB_PATH,
+                        exact);
     if (absolutePath.empty()) {
         // this happens often, we don't want to log an error
         return nullptr;
     }
-    const char* const driver_absolute_path = absolutePath.c_str();
+    const char* const driverAbsolutePath = absolutePath.c_str();
 
-    // Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
-    // the original routine when the namespace does not exist.
-    // See /system/core/rootdir/etc/ld.config.txt for the configuration of the
-    // sphal namespace.
-    void* dso = do_android_load_sphal_library(driver_absolute_path,
-                                              RTLD_NOW | RTLD_LOCAL);
+    // Currently the default driver is unlikely to be ANGLE on most devices,
+    // hence put this first.
+    if (useSphalNamespace) {
+        // Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
+        // the original routine when the namespace does not exist.
+        // See /system/linkerconfig/contents/namespace for the configuration of the
+        // sphal namespace.
+        dso = do_android_load_sphal_library(driverAbsolutePath, RTLD_NOW | RTLD_LOCAL);
+    } else {
+        // Try to load drivers from the default namespace.
+        // See /system/linkerconfig/contents/namespace for the configuration of the
+        // default namespace.
+        dso = do_dlopen(driverAbsolutePath, RTLD_NOW | RTLD_LOCAL);
+    }
+
     if (dso == nullptr) {
         const char* err = dlerror();
-        ALOGE("load_driver(%s): %s", driver_absolute_path, err ? err : "unknown");
+        ALOGE("load_driver(%s): %s", driverAbsolutePath, err ? err : "unknown");
         return nullptr;
     }
 
-    ALOGD("loaded %s", driver_absolute_path);
+    ALOGV("loaded %s", driverAbsolutePath);
 
     return dso;
 }
 
 static void* load_angle(const char* kind, android_namespace_t* ns) {
-    const android_dlextinfo dlextinfo = {
-            .flags = ANDROID_DLEXT_USE_NAMESPACE,
-            .library_namespace = ns,
-    };
-
     std::string name = std::string("lib") + kind + "_angle.so";
+    void* so = nullptr;
 
-    void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
+    if (android::GraphicsEnv::getInstance().shouldUseSystemAngle()) {
+        so = do_dlopen(name.c_str(), RTLD_NOW | RTLD_LOCAL);
+    } else {
+        const android_dlextinfo dlextinfo = {
+                .flags = ANDROID_DLEXT_USE_NAMESPACE,
+                .library_namespace = ns,
+        };
+        so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
+    }
 
     if (so) {
         return so;
@@ -563,10 +584,14 @@
     ATRACE_CALL();
 
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
-    if (!ns) {
+    // ANGLE namespace is used for loading ANGLE from apk, and hence if namespace is not
+    // constructed, it means ANGLE apk is not set to be the OpenGL ES driver.
+    // Hence skip if ANGLE apk and system ANGLE are not set to be the OpenGL ES driver.
+    if (!ns && !android::GraphicsEnv::getInstance().shouldUseSystemAngle()) {
         return nullptr;
     }
 
+    // use ANGLE APK driver
     android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
     driver_t* hnd = nullptr;
 
@@ -588,10 +613,13 @@
 }
 
 void Loader::attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx) {
-    void* pANGLEGetDisplayPlatform = dlsym(dso, "ANGLEGetDisplayPlatform");
-    if (pANGLEGetDisplayPlatform) {
+    cnx->angleGetDisplayPlatformFunc = dlsym(dso, "ANGLEGetDisplayPlatform");
+    cnx->angleResetDisplayPlatformFunc = dlsym(dso, "ANGLEResetDisplayPlatform");
+
+    if (cnx->angleGetDisplayPlatformFunc) {
         ALOGV("ANGLE GLES library loaded");
         cnx->angleLoaded = true;
+        android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
     } else {
         ALOGV("Native GLES library loaded");
         cnx->angleLoaded = false;
@@ -635,7 +663,13 @@
 Loader::driver_t* Loader::attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix,
                                                         const bool exact) {
     ATRACE_CALL();
-    android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::GL);
+    if (suffix && strcmp(suffix, "angle") == 0) {
+        // use system ANGLE driver
+        android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
+    } else {
+        android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::GL);
+    }
+
     driver_t* hnd = nullptr;
     void* dso = load_system_driver("GLES", suffix, exact);
     if (dso) {
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index ed3c616..9905210 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -18,6 +18,7 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <dirent.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -62,12 +63,15 @@
 namespace android {
 
 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                                       const std::string& baseDir)
+                                       size_t maxTotalEntries, const std::string& baseDir)
       : mInitialized(false),
+        mCacheVersion(0),
         mMaxKeySize(maxKeySize),
         mMaxValueSize(maxValueSize),
         mMaxTotalSize(maxTotalSize),
+        mMaxTotalEntries(maxTotalEntries),
         mTotalCacheSize(0),
+        mTotalCacheEntries(0),
         mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
@@ -76,6 +80,26 @@
         return;
     }
 
+    // Set the cache version, override if debug value set
+    mCacheVersion = kMultifileBlobCacheVersion;
+    int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
+    if (debugCacheVersion >= 0) {
+        ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
+        mCacheVersion = debugCacheVersion;
+    }
+
+    // Set the platform build ID, override if debug value set
+    mBuildId = base::GetProperty("ro.build.id", "");
+    std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
+    if (!debugBuildId.empty()) {
+        ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
+        if (debugBuildId.length() > PROP_VALUE_MAX) {
+            ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
+                  PROP_VALUE_MAX);
+        }
+        mBuildId = debugBuildId;
+    }
+
     // Establish the name of our multifile directory
     mMultifileDirName = baseDir + ".multifile";
 
@@ -93,14 +117,30 @@
     mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
 
     // See if the dir exists, and initialize using its contents
+    bool statusGood = false;
+
+    // Check that our cacheVersion and buildId match
     struct stat st;
     if (stat(mMultifileDirName.c_str(), &st) == 0) {
+        if (checkStatus(mMultifileDirName.c_str())) {
+            statusGood = true;
+        } else {
+            ALOGV("INIT: Cache status has changed, clearing the cache");
+            if (!clearCache()) {
+                ALOGE("INIT: Unable to clear cache");
+                return;
+            }
+        }
+    }
+
+    if (statusGood) {
         // Read all the files and gather details, then preload their contents
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
 
@@ -123,7 +163,8 @@
                 if (st.st_size <= 0 || st.st_atime <= 0) {
                     ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -140,7 +181,7 @@
                 MultifileHeader header;
                 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
                 if (result != sizeof(MultifileHeader)) {
-                    ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
+                    ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
                           fullPath.c_str(), std::strerror(errno));
                     close(fd);
                     return;
@@ -150,7 +191,8 @@
                 if (header.magic != kMultifileMagic) {
                     ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     close(fd);
                     continue;
@@ -175,7 +217,7 @@
                 if (header.crc !=
                     crc32c(mappedEntry + sizeof(MultifileHeader),
                            fileSize - sizeof(MultifileHeader))) {
-                    ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
+                    ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
@@ -184,11 +226,12 @@
 
                 // If the cache entry is damaged or no good, remove it
                 if (header.keySize <= 0 || header.valueSize <= 0) {
-                    ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
+                    ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
                           "removing.",
                           entryHash, header.keySize, header.valueSize);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -226,9 +269,17 @@
         // If the multifile directory does not exist, create it and start from scratch
         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
             ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+            return;
+        }
+
+        // Create new status file
+        if (!createStatus(mMultifileDirName.c_str())) {
+            ALOGE("INIT: Failed to create status file!");
+            return;
         }
     }
 
+    ALOGV("INIT: Multifile BlobCache initialization succeeded");
     mInitialized = true;
 }
 
@@ -270,7 +321,7 @@
     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
 
     // If we're going to be over the cache limit, kick off a trim to clear space
-    if (getTotalSize() + fileSize > mMaxTotalSize) {
+    if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
         ALOGV("SET: Cache is full, calling trimCache to clear space");
         trimCache();
     }
@@ -469,6 +520,112 @@
     }
 }
 
+bool MultifileBlobCache::createStatus(const std::string& baseDir) {
+    // Populate the status struct
+    struct MultifileStatus status;
+    memset(&status, 0, sizeof(status));
+    status.magic = kMultifileMagic;
+    status.cacheVersion = mCacheVersion;
+
+    // Copy the buildId string in, up to our allocated space
+    strncpy(status.buildId, mBuildId.c_str(),
+            mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
+
+    // Finally update the crc, using cacheVersion and everything the follows
+    status.crc =
+            crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+                   sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+
+    // Create the status file
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+    int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Write the buffer contents to disk
+    ssize_t result = write(fd, &status, sizeof(status));
+    close(fd);
+    if (result != sizeof(status)) {
+        ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
+    return true;
+}
+
+bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+
+    // Does status exist
+    struct stat st;
+    if (stat(cacheStatus.c_str(), &st) != 0) {
+        ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
+        return false;
+    }
+
+    // If the status entry is damaged or no good, remove it
+    if (st.st_size <= 0 || st.st_atime <= 0) {
+        ALOGE("STATUS(CHECK): Cache status has invalid stats!");
+        return false;
+    }
+
+    // Open the file so we can read its header
+    int fd = open(cacheStatus.c_str(), O_RDONLY);
+    if (fd == -1) {
+        ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
+              cacheStatus.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    // Read in the status header
+    MultifileStatus status;
+    size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
+    close(fd);
+    if (result != sizeof(MultifileStatus)) {
+        ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Verify header magic
+    if (status.magic != kMultifileMagic) {
+        ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
+        return false;
+    }
+
+    // Ensure we have a good CRC
+    if (status.crc !=
+        crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+               sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+        ALOGE("STATUS(CHECK): Cache status failed CRC check!");
+        return false;
+    }
+
+    // Check cacheVersion
+    if (status.cacheVersion != mCacheVersion) {
+        ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
+              mCacheVersion);
+        return false;
+    }
+
+    // Check buildId
+    if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
+        ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
+              mBuildId.c_str());
+        return false;
+    }
+
+    // All checks passed!
+    ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
+          status.cacheVersion, status.buildId, cacheStatus.c_str());
+    return true;
+}
+
 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
                                     time_t accessTime) {
     mEntries.insert(entryHash);
@@ -485,10 +642,12 @@
 
 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize += fileSize;
+    mTotalCacheEntries++;
 }
 
 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize -= fileSize;
+    mTotalCacheEntries--;
 }
 
 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
@@ -557,7 +716,7 @@
     return false;
 }
 
-bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
     // Walk through our map of sorted last access times and remove files until under the limit
     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
         uint32_t entryHash = cacheEntryIter->first;
@@ -590,9 +749,10 @@
 
         // See if it has been reduced enough
         size_t totalCacheSize = getTotalSize();
-        if (totalCacheSize <= cacheLimit) {
+        size_t totalCacheEntries = getTotalEntries();
+        if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
             // Success
-            ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+            ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
             return true;
         }
     }
@@ -601,8 +761,43 @@
     return false;
 }
 
+// Clear the cache by removing all entries and deleting the directory
+bool MultifileBlobCache::clearCache() {
+    DIR* dir;
+    struct dirent* entry;
+    dir = opendir(mMultifileDirName.c_str());
+    if (dir == nullptr) {
+        ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
+        return false;
+    }
+
+    // Delete all entries and the status file
+    while ((entry = readdir(dir)) != nullptr) {
+        if (entry->d_name == "."s || entry->d_name == ".."s) {
+            continue;
+        }
+
+        std::string entryName = entry->d_name;
+        std::string fullPath = mMultifileDirName + "/" + entryName;
+        if (remove(fullPath.c_str()) != 0) {
+            ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+            return false;
+        }
+    }
+
+    // Delete the directory
+    if (remove(mMultifileDirName.c_str()) != 0) {
+        ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("CLEAR: Cleared the multifile blobcache");
+    return true;
+}
+
 // When removing files, what fraction of the overall limit should be reached when removing files
 // A divisor of two will decrease the cache to 50%, four to 25% and so on
+// We use the same limit to manage size and entry count
 constexpr uint32_t kCacheLimitDivisor = 2;
 
 // Calculate the cache size and remove old entries until under the limit
@@ -611,8 +806,9 @@
     ALOGV("TRIM: Waiting for work to complete.");
     waitForWorkComplete();
 
-    ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
-    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
+    ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
+          mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
+    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
         ALOGE("Error when clearing multifile shader cache");
         return;
     }
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 5e527dc..18566c2 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -21,6 +21,7 @@
 #include <EGL/eglext.h>
 
 #include <android-base/thread_annotations.h>
+#include <cutils/properties.h>
 #include <future>
 #include <map>
 #include <queue>
@@ -33,6 +34,9 @@
 
 namespace android {
 
+constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
+
 struct MultifileHeader {
     uint32_t magic;
     uint32_t crc;
@@ -46,6 +50,13 @@
     time_t accessTime;
 };
 
+struct MultifileStatus {
+    uint32_t magic;
+    uint32_t crc;
+    uint32_t cacheVersion;
+    char buildId[PROP_VALUE_MAX];
+};
+
 struct MultifileHotCache {
     int entryFd;
     uint8_t* entryBuffer;
@@ -92,7 +103,7 @@
 class MultifileBlobCache {
 public:
     MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                       const std::string& baseDir);
+                       size_t maxTotalEntries, const std::string& baseDir);
     ~MultifileBlobCache();
 
     void set(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -103,6 +114,13 @@
     void finish();
 
     size_t getTotalSize() const { return mTotalCacheSize; }
+    size_t getTotalEntries() const { return mTotalCacheEntries; }
+
+    const std::string& getCurrentBuildId() const { return mBuildId; }
+    void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
+
+    uint32_t getCurrentCacheVersion() const { return mCacheVersion; }
+    void setCurrentCacheVersion(uint32_t cacheVersion) { mCacheVersion = cacheVersion; }
 
 private:
     void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
@@ -111,6 +129,9 @@
     bool removeEntry(uint32_t entryHash);
     MultifileEntryStats getEntryStats(uint32_t entryHash);
 
+    bool createStatus(const std::string& baseDir);
+    bool checkStatus(const std::string& baseDir);
+
     size_t getFileSize(uint32_t entryHash);
     size_t getValueSize(uint32_t entryHash);
 
@@ -120,12 +141,16 @@
     bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
     bool removeFromHotCache(uint32_t entryHash);
 
+    bool clearCache();
     void trimCache();
-    bool applyLRU(size_t cacheLimit);
+    bool applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit);
 
     bool mInitialized;
     std::string mMultifileDirName;
 
+    std::string mBuildId;
+    uint32_t mCacheVersion;
+
     std::unordered_set<uint32_t> mEntries;
     std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
     std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
@@ -133,7 +158,9 @@
     size_t mMaxKeySize;
     size_t mMaxValueSize;
     size_t mMaxTotalSize;
+    size_t mMaxTotalEntries;
     size_t mTotalCacheSize;
+    size_t mTotalCacheEntries;
     size_t mHotCacheLimit;
     size_t mHotCacheEntryLimit;
     size_t mHotCacheSize;
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 1639be6..90a0f1e 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -16,13 +16,17 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <android-base/test_utils.h>
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 
+#include <fstream>
 #include <memory>
 
+using namespace std::literals;
+
 namespace android {
 
 template <typename T>
@@ -31,23 +35,40 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 class MultifileBlobCacheTest : public ::testing::Test {
 protected:
     virtual void SetUp() {
+        clearProperties();
         mTempFile.reset(new TemporaryFile());
         mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
-                                          &mTempFile->path[0]));
+                                          kMaxTotalEntries, &mTempFile->path[0]));
     }
 
-    virtual void TearDown() { mMBC.reset(); }
+    virtual void TearDown() {
+        clearProperties();
+        mMBC.reset();
+    }
 
     int getFileDescriptorCount();
+    std::vector<std::string> getCacheEntries();
+
+    void clearProperties();
 
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
 };
 
+void MultifileBlobCacheTest::clearProperties() {
+    // Clear any debug properties used in the tests
+    base::SetProperty("debug.egl.blobcache.cache_version", "");
+    base::WaitForProperty("debug.egl.blobcache.cache_version", "");
+
+    base::SetProperty("debug.egl.blobcache.build_id", "");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "");
+}
+
 TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
     unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
     mMBC->set("abcd", 4, "efgh", 4);
@@ -211,6 +232,23 @@
     }
 }
 
+TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
+    // Fill the cache with max entries
+    int i = 0;
+    for (i = 0; i < kMaxTotalEntries; i++) {
+        mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+    }
+
+    // Ensure it is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // Add another entry
+    mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+
+    // Ensure total entries is cut in half + 1
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
+}
+
 TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
     unsigned char buf[1] = {0xee};
     mMBC->set("x", 1, "y", 1);
@@ -234,8 +272,7 @@
 
 TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
     // Populate the cache with a bunch of entries
-    size_t kLargeNumberOfEntries = 1024;
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         // printf("Caching: %i", i);
 
         // Use the index as the key and value
@@ -247,27 +284,223 @@
     }
 
     // Ensure we don't have a bunch of open fds
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
     // Close the cache so everything writes out
     mMBC->finish();
     mMBC.reset();
 
     // Now open it again and ensure we still don't have a bunch of open fds
-    mMBC.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
 
     // Check after initialization
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         int result = 0;
         ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
         ASSERT_EQ(i, result);
     }
 
     // And again after we've actually used it
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+}
+
+std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+    std::vector<std::string> cacheEntries;
+
+    struct stat info;
+    if (stat(multifileDirName.c_str(), &info) == 0) {
+        // We have a multifile dir. Skip the status file and return the only entry.
+        DIR* dir;
+        struct dirent* entry;
+        if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+            while ((entry = readdir(dir)) != nullptr) {
+                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                    continue;
+                }
+                if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+                    continue;
+                }
+                cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
+            }
+        } else {
+            printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
+                   std::strerror(errno));
+        }
+    } else {
+        printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
+    }
+
+    return cacheEntries;
+}
+
+TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
+    struct stat info;
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // After INIT, cache should have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure status lives after closing the cache
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we still have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+}
+
+// Verify missing cache status file causes cache the be cleared
+TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Delete the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+    remove(statusFile.str().c_str());
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file BEGIN causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the beginning of the cache file
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(0, std::ios_base::beg);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file END causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the END of the cache status file, modifying its contents
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(-strlen(stomp), std::ios_base::end);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched cacheVersion causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug cacheVersion
+    std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+    ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+    ASSERT_TRUE(
+            base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched buildId causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug buildId
+    base::SetProperty("debug.egl.blobcache.build_id", "foo");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
 }
 
 } // namespace android
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index ee605c2..f0054a7 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -35,12 +35,6 @@
 
 namespace angle {
 
-constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
-constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
-
-static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
-static ResetDisplayPlatformFunc angleResetDisplayPlatform = nullptr;
-
 static time_t startTime = time(nullptr);
 
 static const unsigned char* getTraceCategoryEnabledFlag(PlatformMethods* /*platform*/,
@@ -111,50 +105,19 @@
 }
 
 // Initialize function ptrs for ANGLE PlatformMethods struct, used for systrace
-bool initializeAnglePlatform(EGLDisplay dpy) {
-    // Since we're inside libEGL, use dlsym to lookup fptr for ANGLEGetDisplayPlatform
-    android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
-    void* so = nullptr;
-    if (ns) {
-        const android_dlextinfo dlextinfo = {
-                .flags = ANDROID_DLEXT_USE_NAMESPACE,
-                .library_namespace = ns,
-        };
-        so = android_dlopen_ext(kAngleEs2Lib, kAngleDlFlags, &dlextinfo);
-        if (so) {
-            ALOGD("dlopen_ext from APK (%s) success at %p", kAngleEs2Lib, so);
-        } else {
-            ALOGE("dlopen_ext(\"%s\") failed: %s", kAngleEs2Lib, dlerror());
-            return false;
-        }
-    } else {
-        // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
-        so = android_load_sphal_library(kAngleEs2Lib, kAngleDlFlags);
-        if (so) {
-            ALOGD("dlopen (%s) success at %p", kAngleEs2Lib, so);
-        } else {
-            ALOGE("%s failed to dlopen %s: %s!", __FUNCTION__, kAngleEs2Lib, dlerror());
-            return false;
-        }
-    }
-
-    angleGetDisplayPlatform =
-            reinterpret_cast<GetDisplayPlatformFunc>(dlsym(so, "ANGLEGetDisplayPlatform"));
-
-    if (!angleGetDisplayPlatform) {
-        ALOGE("dlsym lookup of ANGLEGetDisplayPlatform in libEGL_angle failed!");
-        dlclose(so);
+bool initializeAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx) {
+    if (cnx->angleGetDisplayPlatformFunc == nullptr) {
+        ALOGE("ANGLEGetDisplayPlatform is not initialized!");
         return false;
     }
 
-    angleResetDisplayPlatform =
-            reinterpret_cast<ResetDisplayPlatformFunc>(dlsym(so, "ANGLEResetDisplayPlatform"));
+    GetDisplayPlatformFunc angleGetDisplayPlatform =
+            reinterpret_cast<GetDisplayPlatformFunc>(cnx->angleGetDisplayPlatformFunc);
 
     PlatformMethods* platformMethods = nullptr;
     if (!((angleGetDisplayPlatform)(dpy, g_PlatformMethodNames, g_NumPlatformMethods, nullptr,
                                     &platformMethods))) {
         ALOGE("ANGLEGetDisplayPlatform call failed!");
-        dlclose(so);
         return false;
     }
     if (platformMethods) {
@@ -166,8 +129,10 @@
     return true;
 }
 
-void resetAnglePlatform(EGLDisplay dpy) {
-    if (angleResetDisplayPlatform) {
+void resetAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx) {
+    if (cnx->angleResetDisplayPlatformFunc) {
+        ResetDisplayPlatformFunc angleResetDisplayPlatform =
+                reinterpret_cast<ResetDisplayPlatformFunc>(cnx->angleResetDisplayPlatformFunc);
         angleResetDisplayPlatform(dpy);
     }
 }
diff --git a/opengl/libs/EGL/egl_angle_platform.h b/opengl/libs/EGL/egl_angle_platform.h
index 6c24aa5..63806c2 100644
--- a/opengl/libs/EGL/egl_angle_platform.h
+++ b/opengl/libs/EGL/egl_angle_platform.h
@@ -25,8 +25,8 @@
 
 namespace angle {
 
-bool initializeAnglePlatform(EGLDisplay dpy);
-void resetAnglePlatform(EGLDisplay dpy);
+bool initializeAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx);
+void resetAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx);
 
 }; // namespace angle
 
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 1b68344..98ff1d1 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -41,6 +41,7 @@
 constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileTotalEntries = 4 * 1024;
 
 namespace android {
 
@@ -277,7 +278,7 @@
     if (mMultifileBlobCache == nullptr) {
         mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize,
                                                          kMaxMultifileValueSize, mCacheByteLimit,
-                                                         mFilename));
+                                                         kMaxMultifileTotalEntries, mFilename));
     }
     return mMultifileBlobCache.get();
 }
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 3317347..1ada33e 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -64,11 +64,6 @@
     return false;
 }
 
-bool needsAndroidPEglMitigation() {
-    static const int32_t vndk_version = base::GetIntProperty("ro.vndk.version", -1);
-    return vndk_version <= 28;
-}
-
 int egl_get_init_count(EGLDisplay dpy) {
     egl_display_t* eglDisplay = egl_display_t::get(dpy);
     return eglDisplay ? eglDisplay->getRefsCount() : 0;
@@ -168,7 +163,7 @@
         if (dpy == EGL_NO_DISPLAY) {
             ALOGE("eglGetPlatformDisplay failed!");
         } else {
-            if (!angle::initializeAnglePlatform(dpy)) {
+            if (!angle::initializeAnglePlatform(dpy, cnx)) {
                 ALOGE("initializeAnglePlatform failed!");
             }
         }
@@ -365,14 +360,6 @@
             if (len) {
                 // NOTE: we could avoid the copy if we had strnstr.
                 const std::string ext(start, len);
-                // Mitigation for Android P vendor partitions: Adreno 530 driver shipped on
-                // some Android P vendor partitions this extension under the draft KHR name,
-                // but during Khronos review it was decided to demote it to EXT.
-                if (needsAndroidPEglMitigation() && ext == "EGL_EXT_image_gl_colorspace" &&
-                    findExtension(disp.queryString.extensions, "EGL_KHR_image_gl_colorspace")) {
-                    mExtensionString.append("EGL_EXT_image_gl_colorspace ");
-                }
-
                 if (findExtension(disp.queryString.extensions, ext.c_str(), len)) {
                     mExtensionString.append(ext + " ");
                 }
@@ -433,7 +420,7 @@
         if (cnx->dso && disp.state == egl_display_t::INITIALIZED) {
             // If we're using ANGLE reset any custom DisplayPlatform
             if (cnx->angleLoaded) {
-                angle::resetAnglePlatform(disp.dpy);
+                angle::resetAnglePlatform(disp.dpy, cnx);
             }
             if (cnx->egl.eglTerminate(disp.dpy) == EGL_FALSE) {
                 ALOGW("eglTerminate(%p) failed (%s)", disp.dpy,
diff --git a/opengl/libs/EGL/egl_display.h b/opengl/libs/EGL/egl_display.h
index 87c2176..867a117 100644
--- a/opengl/libs/EGL/egl_display.h
+++ b/opengl/libs/EGL/egl_display.h
@@ -39,7 +39,6 @@
 struct egl_connection_t;
 
 bool findExtension(const char* exts, const char* name, size_t nameLen = 0);
-bool needsAndroidPEglMitigation();
 
 class EGLAPI egl_display_t { // marked as EGLAPI for testing purposes
     static std::map<EGLDisplay, std::unique_ptr<egl_display_t>> displayMap;
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 440eb17..a6af713 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -1644,26 +1644,6 @@
     const egl_display_t* dp = validate_display(dpy);
     if (!dp) return EGL_NO_IMAGE_KHR;
 
-    std::vector<AttrType> strippedAttribs;
-    if (needsAndroidPEglMitigation()) {
-        // Mitigation for Android P vendor partitions: eglImageCreateKHR should accept
-        // EGL_GL_COLORSPACE_LINEAR_KHR, EGL_GL_COLORSPACE_SRGB_KHR and
-        // EGL_GL_COLORSPACE_DEFAULT_EXT if EGL_EXT_image_gl_colorspace is supported,
-        // but some drivers don't like the DEFAULT value and generate an error.
-        for (const AttrType* attr = attrib_list; attr && attr[0] != EGL_NONE; attr += 2) {
-            if (attr[0] == EGL_GL_COLORSPACE_KHR &&
-                dp->haveExtension("EGL_EXT_image_gl_colorspace")) {
-                if (attr[1] != EGL_GL_COLORSPACE_LINEAR_KHR &&
-                    attr[1] != EGL_GL_COLORSPACE_SRGB_KHR) {
-                    continue;
-                }
-            }
-            strippedAttribs.push_back(attr[0]);
-            strippedAttribs.push_back(attr[1]);
-        }
-        strippedAttribs.push_back(EGL_NONE);
-    }
-
     ContextRef _c(dp, ctx);
     egl_context_t* const c = _c.get();
 
@@ -1671,8 +1651,7 @@
     egl_connection_t* const cnx = &gEGLImpl;
     if (cnx->dso && eglCreateImageFunc) {
         result = eglCreateImageFunc(dp->disp.dpy, c ? c->context : EGL_NO_CONTEXT, target, buffer,
-                                    needsAndroidPEglMitigation() ? strippedAttribs.data()
-                                                                 : attrib_list);
+                                    attrib_list);
     }
     return result;
 }
diff --git a/opengl/libs/EGL/egldefs.h b/opengl/libs/EGL/egldefs.h
index 3bd37cb..90a3c19 100644
--- a/opengl/libs/EGL/egldefs.h
+++ b/opengl/libs/EGL/egldefs.h
@@ -42,7 +42,9 @@
             libGles1(nullptr),
             libGles2(nullptr),
             systemDriverUnloaded(false),
-            angleLoaded(false) {
+            angleLoaded(false),
+            angleGetDisplayPlatformFunc(nullptr),
+            angleResetDisplayPlatformFunc(nullptr) {
         const char* const* entries = platform_names;
         EGLFuncPointer* curr = reinterpret_cast<EGLFuncPointer*>(&platform);
         while (*entries) {
@@ -75,6 +77,9 @@
 
     bool systemDriverUnloaded;
     bool angleLoaded; // Was ANGLE successfully loaded
+
+    void* angleGetDisplayPlatformFunc;
+    void* angleResetDisplayPlatformFunc;
 };
 
 extern gl_hooks_t gHooks[2];
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
index 633cc9c..2d9ee3a 100644
--- a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -28,6 +28,7 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     // To fuzz this, we're going to create a key/value pair from data
@@ -79,8 +80,8 @@
     std::unique_ptr<MultifileBlobCache> mbc;
 
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     // With remaining data, select different paths below
     int loopCount = 1;
     uint8_t bumpCount = 0;
@@ -131,8 +132,8 @@
     // Place the maxKey/maxValue twice
     // The first will fit, the second will trigger hot cache trimming
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     uint8_t* buffer = new uint8_t[kMaxValueSize];
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
@@ -145,7 +146,7 @@
     // overflow
     tempFile.reset(new TemporaryFile());
     mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
-                                     &tempFile->path[0]));
+                                     kMaxTotalEntries, &tempFile->path[0]));
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
     mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index f81c68f..ce58182 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -114,25 +114,26 @@
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
         // Ensure we only have one file to manage
-        int realFileCount = 0;
+        int entryFileCount = 0;
 
-        // We have a multifile dir. Return the only real file in it.
+        // We have a multifile dir. Return the only entry file in it.
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
                 cachefileName = multifileDirName + "/" + entry->d_name;
-                realFileCount++;
+                entryFileCount++;
             }
         } else {
             printf("Unable to open %s, error: %s\n",
                    multifileDirName.c_str(), std::strerror(errno));
         }
 
-        if (realFileCount != 1) {
+        if (entryFileCount != 1) {
             // If there was more than one real file in the directory, this
             // violates test assumptions
             cachefileName = "";
diff --git a/opengl/tests/gldual/AndroidManifest.xml b/opengl/tests/gldual/AndroidManifest.xml
index a36f4f7..d6335b0 100644
--- a/opengl/tests/gldual/AndroidManifest.xml
+++ b/opengl/tests/gldual/AndroidManifest.xml
@@ -20,8 +20,9 @@
             android:label="@string/gldual_activity">
         <activity android:name="GLDualActivity"
                 android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-            	android:launchMode="singleTask"
-            	android:configChanges="orientation|keyboardHidden">
+                android:launchMode="singleTask"
+                android:configChanges="orientation|keyboardHidden"
+                android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h
index bf6189d..654c903 100644
--- a/services/batteryservice/include/batteryservice/BatteryService.h
+++ b/services/batteryservice/include/batteryservice/BatteryService.h
@@ -38,6 +38,7 @@
     BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
     BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
     BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH
+    BATTERY_PROP_PART_STATUS = 12, // equals BATTERY_PROPERTY_PART_STATUS
 };
 
 struct BatteryProperties {
diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp
index f06a045..11b636d 100644
--- a/services/gpuservice/gpustats/GpuStats.cpp
+++ b/services/gpuservice/gpustats/GpuStats.cpp
@@ -163,11 +163,13 @@
         addLoadingTime(driver, driverLoadingTime, &appInfo);
         appInfo.appPackageName = appPackageName;
         appInfo.driverVersionCode = driverVersionCode;
-        appInfo.angleInUse = driverPackageName == "angle";
+        appInfo.angleInUse =
+                driver == GpuStatsInfo::Driver::ANGLE || driverPackageName == "angle";
         appInfo.lastAccessTime = std::chrono::system_clock::now();
         mAppStats.insert({appStatsKey, appInfo});
     } else {
-        mAppStats[appStatsKey].angleInUse = driverPackageName == "angle";
+        mAppStats[appStatsKey].angleInUse =
+                driver == GpuStatsInfo::Driver::ANGLE || driverPackageName == "angle";
         addLoadingTime(driver, driverLoadingTime, &mAppStats[appStatsKey]);
         mAppStats[appStatsKey].lastAccessTime = std::chrono::system_clock::now();
     }
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 76729ef..69f42bc 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -76,6 +76,8 @@
     srcs: [
         "InputCommonConverter.cpp",
         "InputDeviceMetricsCollector.cpp",
+        "InputFilter.cpp",
+        "InputFilterCallbacks.cpp",
         "InputProcessor.cpp",
         "PointerChoreographer.cpp",
         "PreferStylusOverTouchBlocker.cpp",
@@ -243,6 +245,9 @@
         "Bug-115739809",
         "StructLayout_test",
 
+        // jni
+        "libservices.core",
+
         // rust targets
         "libinput_rust_test",
 
@@ -256,6 +261,7 @@
         "inputflinger_input_reader_fuzzer",
         "inputflinger_blocking_queue_fuzzer",
         "inputflinger_input_classifier_fuzzer",
+        "inputflinger_input_dispatcher_fuzzer",
 
         // Java/Kotlin targets
         "CtsWindowManagerDeviceWindow",
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/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
new file mode 100644
index 0000000..72c6f1a
--- /dev/null
+++ b/services/inputflinger/InputFilter.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputFilter"
+
+#include "InputFilter.h"
+
+namespace android {
+
+using aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::KeyEventAction;
+using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
+using aidl::android::hardware::input::common::Source;
+
+AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) {
+    AidlKeyEvent event;
+    event.id = args.id;
+    event.eventTime = args.eventTime;
+    event.deviceId = args.deviceId;
+    event.source = static_cast<Source>(args.source);
+    event.displayId = args.displayId;
+    event.policyFlags = args.policyFlags;
+    event.action = static_cast<KeyEventAction>(args.action);
+    event.flags = args.flags;
+    event.keyCode = args.keyCode;
+    event.scanCode = args.scanCode;
+    event.metaState = args.metaState;
+    event.downTime = args.downTime;
+    event.readTime = args.readTime;
+    return event;
+}
+
+InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy)
+      : mNextListener(listener),
+        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener, policy)),
+        mPolicy(policy) {
+    LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
+    LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
+}
+
+void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    mDeviceInfos.clear();
+    mDeviceInfos.reserve(args.inputDeviceInfos.size());
+    for (auto info : args.inputDeviceInfos) {
+        AidlDeviceInfo& aidlInfo = mDeviceInfos.emplace_back();
+        aidlInfo.deviceId = info.getId();
+        aidlInfo.external = info.isExternal();
+    }
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk());
+    }
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyKey(const NotifyKeyArgs& args) {
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
+        return;
+    }
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyMotion(const NotifyMotionArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifySwitch(const NotifySwitchArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifySensor(const NotifySensorArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    mNextListener.notify(args);
+}
+
+void InputFilter::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+    mNextListener.notify(args);
+}
+
+bool InputFilter::isFilterEnabled() {
+    bool result;
+    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->isEnabled(&result).isOk());
+    return result;
+}
+
+void InputFilter::setAccessibilityBounceKeysThreshold(nsecs_t threshold) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.bounceKeysThresholdNs != threshold) {
+        mConfig.bounceKeysThresholdNs = threshold;
+        notifyConfigurationChangedLocked();
+    }
+}
+
+void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.stickyKeysEnabled != enabled) {
+        mConfig.stickyKeysEnabled = enabled;
+        notifyConfigurationChangedLocked();
+        if (!enabled) {
+            // When Sticky keys is disabled, send callback to clear any saved sticky state.
+            mPolicy.notifyStickyModifierStateChanged(0, 0);
+        }
+    }
+}
+
+void InputFilter::notifyConfigurationChangedLocked() {
+    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk());
+    }
+}
+
+void InputFilter::dump(std::string& dump) {
+    dump += "InputFilter:\n";
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
new file mode 100644
index 0000000..153d29d
--- /dev/null
+++ b/services/inputflinger/InputFilter.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <utils/Mutex.h>
+#include "InputFilterCallbacks.h"
+#include "InputFilterPolicyInterface.h"
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+namespace android {
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust implementation.
+ */
+class InputFilterInterface : public InputListenerInterface {
+public:
+    /**
+     * This method may be called on any thread (usually by the input manager on a binder thread).
+     */
+    virtual void dump(std::string& dump) = 0;
+    virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
+};
+
+class InputFilter : public InputFilterInterface {
+public:
+    using IInputFlingerRust = aidl::com::android::server::inputflinger::IInputFlingerRust;
+    using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+    using IInputFilterCallbacks =
+            aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks;
+    using InputFilterConfiguration =
+            aidl::com::android::server::inputflinger::InputFilterConfiguration;
+    using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
+
+    explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust,
+                         InputFilterPolicyInterface& policy);
+    ~InputFilter() override = default;
+    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
+    void notifyKey(const NotifyKeyArgs& args) override;
+    void notifyMotion(const NotifyMotionArgs& args) override;
+    void notifySwitch(const NotifySwitchArgs& args) override;
+    void notifySensor(const NotifySensorArgs& args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs& args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
+    void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilityStickyKeysEnabled(bool enabled) override;
+    void dump(std::string& dump) override;
+
+private:
+    InputListenerInterface& mNextListener;
+    std::shared_ptr<InputFilterCallbacks> mCallbacks;
+    InputFilterPolicyInterface& mPolicy;
+    std::shared_ptr<IInputFilter> mInputFilterRust;
+    // Keep track of connected peripherals, so that if filters are enabled later, we can pass that
+    // info to the filters
+    std::vector<AidlDeviceInfo> mDeviceInfos;
+    mutable std::mutex mLock;
+    InputFilterConfiguration mConfig GUARDED_BY(mLock);
+
+    bool isFilterEnabled();
+    void notifyConfigurationChangedLocked() REQUIRES(mLock);
+};
+
+} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
new file mode 100644
index 0000000..a8759b7
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputFilterCallbacks"
+
+#include "InputFilterCallbacks.h"
+
+namespace android {
+
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
+    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
+                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
+                         event.scanCode, event.metaState, event.downTime);
+}
+
+InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener,
+                                           InputFilterPolicyInterface& policy)
+      : mNextListener(listener), mPolicy(policy) {}
+
+ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) {
+    mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus InputFilterCallbacks::onModifierStateChanged(int32_t modifierState,
+                                                                int32_t lockedModifierState) {
+    std::scoped_lock _l(mLock);
+    mStickyModifierState.modifierState = modifierState;
+    mStickyModifierState.lockedModifierState = lockedModifierState;
+    mPolicy.notifyStickyModifierStateChanged(modifierState, lockedModifierState);
+    ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d",
+          modifierState, lockedModifierState);
+    return ndk::ScopedAStatus::ok();
+}
+
+uint32_t InputFilterCallbacks::getModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.modifierState;
+}
+
+uint32_t InputFilterCallbacks::getLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.lockedModifierState;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
new file mode 100644
index 0000000..31c160a
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_auto_utils.h>
+#include <utils/Mutex.h>
+#include <mutex>
+#include "InputFilterPolicyInterface.h"
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust callback implementation.
+ */
+namespace android {
+
+using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
+public:
+    explicit InputFilterCallbacks(InputListenerInterface& listener,
+                                  InputFilterPolicyInterface& policy);
+    ~InputFilterCallbacks() override = default;
+
+    uint32_t getModifierState();
+    uint32_t getLockedModifierState();
+
+private:
+    InputListenerInterface& mNextListener;
+    InputFilterPolicyInterface& mPolicy;
+    mutable std::mutex mLock;
+    struct StickyModifierState {
+        uint32_t modifierState;
+        uint32_t lockedModifierState;
+    } mStickyModifierState GUARDED_BY(mLock);
+
+    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
+    ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
+                                              int32_t lockedModifierState) override;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 1f17c16..016ae04 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -39,7 +39,7 @@
 
 // Helper to std::visit with lambdas.
 template <typename... V>
-struct Visitor : V... {};
+struct Visitor : V... { using V::operator()...; };
 // explicit deduction guide (not needed as of C++20)
 template <typename... V>
 Visitor(V...) -> Visitor<V...>;
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 92c65e1..4863513 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -42,6 +42,7 @@
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
 const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
 
 int32_t exceptionCodeFromStatusT(status_t status) {
     switch (status) {
@@ -118,6 +119,7 @@
  * The event flow is via the "InputListener" interface, as follows:
  *   InputReader
  *     -> UnwantedInteractionBlocker
+ *     -> InputFilter
  *     -> PointerChoreographer
  *     -> InputProcessor
  *     -> InputDeviceMetricsCollector
@@ -125,13 +127,21 @@
  */
 InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                            InputDispatcherPolicyInterface& dispatcherPolicy,
-                           PointerChoreographerPolicyInterface& choreographerPolicy) {
+                           PointerChoreographerPolicyInterface& choreographerPolicy,
+                           InputFilterPolicyInterface& inputFilterPolicy) {
     mInputFlingerRust = createInputFlingerRust();
 
     mDispatcher = createInputDispatcher(dispatcherPolicy);
     mTracingStages.emplace_back(
             std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
 
+    if (ENABLE_INPUT_FILTER_RUST) {
+        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust,
+                                                     inputFilterPolicy);
+        mTracingStages.emplace_back(
+                std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
+    }
+
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
         mCollector = std::make_unique<InputDeviceMetricsCollector>(*mTracingStages.back());
         mTracingStages.emplace_back(
@@ -216,10 +226,17 @@
     return *mDispatcher;
 }
 
+InputFilterInterface& InputManager::getInputFilter() {
+    return *mInputFilter;
+}
+
 void InputManager::monitor() {
     mReader->monitor();
     mBlocker->monitor();
     mProcessor->monitor();
+    if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
+        mCollector->monitor();
+    }
     mDispatcher->monitor();
 }
 
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 20b9fd5..df944ef 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -21,6 +21,7 @@
  */
 
 #include "InputDeviceMetricsCollector.h"
+#include "InputFilter.h"
 #include "InputProcessor.h"
 #include "InputReaderBase.h"
 #include "PointerChoreographer.h"
@@ -28,6 +29,7 @@
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
+#include <InputFilterPolicyInterface.h>
 #include <PointerChoreographerPolicyInterface.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
@@ -40,6 +42,7 @@
 
 using android::os::BnInputFlinger;
 
+using aidl::com::android::server::inputflinger::IInputFilter;
 using aidl::com::android::server::inputflinger::IInputFlingerRust;
 
 namespace android {
@@ -100,6 +103,9 @@
     /* Gets the input dispatcher. */
     virtual InputDispatcherInterface& getDispatcher() = 0;
 
+    /* Gets the input filter */
+    virtual InputFilterInterface& getInputFilter() = 0;
+
     /* Check that the input stages have not deadlocked. */
     virtual void monitor() = 0;
 
@@ -114,7 +120,8 @@
 public:
     InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
                  InputDispatcherPolicyInterface& dispatcherPolicy,
-                 PointerChoreographerPolicyInterface& choreographerPolicy);
+                 PointerChoreographerPolicyInterface& choreographerPolicy,
+                 InputFilterPolicyInterface& inputFilterPolicy);
 
     status_t start() override;
     status_t stop() override;
@@ -124,6 +131,7 @@
     InputProcessorInterface& getProcessor() override;
     InputDeviceMetricsCollectorInterface& getMetricsCollector() override;
     InputDispatcherInterface& getDispatcher() override;
+    InputFilterInterface& getInputFilter() override;
     void monitor() override;
     void dump(std::string& dump) override;
 
@@ -137,6 +145,8 @@
 
     std::unique_ptr<UnwantedInteractionBlockerInterface> mBlocker;
 
+    std::unique_ptr<InputFilterInterface> mInputFilter;
+
     std::unique_ptr<PointerChoreographerInterface> mChoreographer;
 
     std::unique_ptr<InputProcessorInterface> mProcessor;
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index c34cd53..de836e9 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -190,7 +190,7 @@
 
 // Helper to std::visit with lambdas.
 template <typename... V>
-struct Visitor : V... {};
+struct Visitor : V... { using V::operator()...; };
 // explicit deduction guide (not needed as of C++20)
 template <typename... V>
 Visitor(V...) -> Visitor<V...>;
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index e411abb..0be4c32 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -16,17 +16,56 @@
 
 #define LOG_TAG "PointerChoreographer"
 
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
 #include "PointerChoreographer.h"
 
+#define INDENT "  "
+
 namespace android {
 
+namespace {
+bool isFromMouse(const NotifyMotionArgs& args) {
+    return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+            args.pointerProperties[0].toolType == ToolType::MOUSE;
+}
+
+bool isFromTouchpad(const NotifyMotionArgs& args) {
+    return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+            args.pointerProperties[0].toolType == ToolType::FINGER;
+}
+
+bool isHoverAction(int32_t action) {
+    return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isStylusHoverEvent(const NotifyMotionArgs& args) {
+    return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
+}
+} // namespace
+
 // --- PointerChoreographer ---
 
 PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
                                            PointerChoreographerPolicyInterface& policy)
-      : mNextListener(listener) {}
+      : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+            return mPolicy.createPointerController(
+                    PointerControllerInterface::ControllerType::TOUCH);
+        }),
+        mNextListener(listener),
+        mPolicy(policy),
+        mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
+        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+        mShowTouchesEnabled(false),
+        mStylusPointerIconEnabled(false) {}
 
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+    std::scoped_lock _l(mLock);
+
+    mInputDeviceInfos = args.inputDeviceInfos;
+    updatePointerControllersLocked();
     mNextListener.notify(args);
 }
 
@@ -39,7 +78,153 @@
 }
 
 void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
-    mNextListener.notify(args);
+    NotifyMotionArgs newArgs = processMotion(args);
+
+    mNextListener.notify(newArgs);
+}
+
+NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
+    std::scoped_lock _l(mLock);
+
+    if (isFromMouse(args)) {
+        return processMouseEventLocked(args);
+    } else if (isFromTouchpad(args)) {
+        return processTouchpadEventLocked(args);
+    } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+        processStylusHoverEventLocked(args);
+    } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+        processTouchscreenAndStylusEventLocked(args);
+    }
+    return args;
+}
+
+NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
+    if (args.getPointerCount() != 1) {
+        LOG(FATAL) << "Only mouse events with a single pointer are currently supported: "
+                   << args.dump();
+    }
+
+    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+
+    const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    pc.move(deltaX, deltaY);
+    pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+    const auto [x, y] = pc.getPosition();
+    NotifyMotionArgs newArgs(args);
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+    newArgs.xCursorPosition = x;
+    newArgs.yCursorPosition = y;
+    newArgs.displayId = displayId;
+    return newArgs;
+}
+
+NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+    auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+
+    NotifyMotionArgs newArgs(args);
+    newArgs.displayId = displayId;
+    if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
+        // This is a movement of the mouse pointer.
+        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        pc.move(deltaX, deltaY);
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+        const auto [x, y] = pc.getPosition();
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    } else {
+        // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+        const auto [x, y] = pc.getPosition();
+        for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
+            newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                  args.pointerCoords[i].getX() + x);
+            newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                  args.pointerCoords[i].getY() + y);
+        }
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    }
+    return newArgs;
+}
+
+/**
+ * When screen is touched, fade the mouse pointer on that display. We only call fade for
+ * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
+ * mouse device keeps moving and unfades the cursor.
+ * For touch events, we do not need to populate the cursor position.
+ */
+void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
+    if (args.displayId == ADISPLAY_ID_NONE) {
+        return;
+    }
+
+    if (const auto it = mMousePointersByDisplay.find(args.displayId);
+        it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
+        it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+    }
+
+    if (!mShowTouchesEnabled) {
+        return;
+    }
+
+    // Get the touch pointer controller for the device, or create one if it doesn't exist.
+    auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+
+    PointerControllerInterface& pc = *it->second;
+
+    const PointerCoords* coords = args.pointerCoords.data();
+    const int32_t maskedAction = MotionEvent::getActionMasked(args.action);
+    const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
+    std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
+    BitSet32 idBits;
+    if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) {
+        for (size_t i = 0; i < args.getPointerCount(); i++) {
+            if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
+                continue;
+            }
+            uint32_t id = args.pointerProperties[i].id;
+            idToIndex[id] = i;
+            idBits.markBit(id);
+        }
+    }
+    // The PointerController already handles setting spots per-display, so
+    // we do not need to manually manage display changes for touch spots for now.
+    pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId);
+}
+
+void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
+    if (args.displayId == ADISPLAY_ID_NONE) {
+        return;
+    }
+
+    if (args.getPointerCount() != 1) {
+        LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: "
+                     << args.dump();
+    }
+
+    // Get the stylus pointer controller for the device, or create one if it doesn't exist.
+    auto [it, _] =
+            mStylusPointersByDevice.try_emplace(args.deviceId,
+                                                getStylusControllerConstructor(args.displayId));
+
+    PointerControllerInterface& pc = *it->second;
+
+    const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+    const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+    pc.setPosition(x, y);
+    if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+    } else {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
 }
 
 void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
@@ -55,16 +240,304 @@
 }
 
 void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    processDeviceReset(args);
+
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
+    std::scoped_lock _l(mLock);
+    mTouchPointersByDevice.erase(args.deviceId);
+    mStylusPointersByDevice.erase(args.deviceId);
+}
+
 void PointerChoreographer::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
+    if (args.request.enable) {
+        std::scoped_lock _l(mLock);
+        for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
+            mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
+    }
     mNextListener.notify(args);
 }
 
 void PointerChoreographer::dump(std::string& dump) {
+    std::scoped_lock _l(mLock);
+
     dump += "PointerChoreographer:\n";
+    dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
+    dump += StringPrintf("stylus pointer icon enabled: %s\n",
+                         mStylusPointerIconEnabled ? "true" : "false");
+
+    dump += INDENT "MousePointerControllers:\n";
+    for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
+        std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
+        dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
+    }
+    dump += INDENT "TouchPointerControllers:\n";
+    for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
+    dump += INDENT "StylusPointerControllers:\n";
+    for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
+    dump += "\n";
+}
+
+const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const {
+    for (auto& viewport : mViewports) {
+        if (viewport.displayId == displayId) {
+            return &viewport;
+        }
+    }
+    return nullptr;
+}
+
+int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const {
+    return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
+}
+
+std::pair<int32_t, PointerControllerInterface&>
+PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) {
+    const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
+
+    // Get the mouse pointer controller for the display, or create one if it doesn't exist.
+    auto [it, emplaced] =
+            mMousePointersByDisplay.try_emplace(displayId,
+                                                getMouseControllerConstructor(displayId));
+    if (emplaced) {
+        notifyPointerDisplayIdChangedLocked();
+    }
+
+    return {displayId, *it->second};
+}
+
+InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
+    auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+                           [deviceId](const auto& info) { return info.getId() == deviceId; });
+    return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
+}
+
+void PointerChoreographer::updatePointerControllersLocked() {
+    std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+    std::set<DeviceId> touchDevicesToKeep;
+    std::set<DeviceId> stylusDevicesToKeep;
+
+    // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
+    // new PointerControllers if necessary.
+    for (const auto& info : mInputDeviceInfos) {
+        const uint32_t sources = info.getSources();
+        if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
+            isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
+            const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
+            mouseDisplaysToKeep.insert(displayId);
+            // For mice, show the cursor immediately when the device is first connected or
+            // when it moves to a new display.
+            auto [mousePointerIt, isNewMousePointer] =
+                    mMousePointersByDisplay.try_emplace(displayId,
+                                                        getMouseControllerConstructor(displayId));
+            auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
+            if (isNewMouseDevice || isNewMousePointer) {
+                mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+            }
+        }
+        if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
+            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            touchDevicesToKeep.insert(info.getId());
+        }
+        if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
+            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            stylusDevicesToKeep.insert(info.getId());
+        }
+    }
+
+    // Remove PointerControllers no longer needed.
+    std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) {
+        return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end();
+    });
+    std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
+        return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end();
+    });
+    std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
+        return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
+    });
+    std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
+        return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+                            [id](const auto& info) { return info.getId() == id; }) ==
+                mInputDeviceInfos.end();
+    });
+
+    // Notify the policy if there's a change on the pointer display ID.
+    notifyPointerDisplayIdChangedLocked();
+}
+
+void PointerChoreographer::notifyPointerDisplayIdChangedLocked() {
+    int32_t displayIdToNotify = ADISPLAY_ID_NONE;
+    FloatPoint cursorPosition = {0, 0};
+    if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
+        it != mMousePointersByDisplay.end()) {
+        const auto& pointerController = it->second;
+        // Use the displayId from the pointerController, because it accurately reflects whether
+        // the viewport has been added for that display. Otherwise, we would have to check if
+        // the viewport exists separately.
+        displayIdToNotify = pointerController->getDisplayId();
+        cursorPosition = pointerController->getPosition();
+    }
+
+    if (mNotifiedPointerDisplayId == displayIdToNotify) {
+        return;
+    }
+    mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition);
+    mNotifiedPointerDisplayId = displayIdToNotify;
+}
+
+void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
+    std::scoped_lock _l(mLock);
+
+    mDefaultMouseDisplayId = displayId;
+    updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
+    std::scoped_lock _l(mLock);
+    for (const auto& viewport : viewports) {
+        const int32_t displayId = viewport.displayId;
+        if (const auto it = mMousePointersByDisplay.find(displayId);
+            it != mMousePointersByDisplay.end()) {
+            it->second->setDisplayViewport(viewport);
+        }
+        for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+            const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+            if (info && info->getAssociatedDisplayId() == displayId) {
+                stylusPointerController->setDisplayViewport(viewport);
+            }
+        }
+    }
+    mViewports = viewports;
+    notifyPointerDisplayIdChangedLocked();
+}
+
+std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
+        int32_t associatedDisplayId) {
+    std::scoped_lock _l(mLock);
+    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
+    if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
+        return *viewport;
+    }
+    return std::nullopt;
+}
+
+FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
+    std::scoped_lock _l(mLock);
+    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
+    if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
+        it != mMousePointersByDisplay.end()) {
+        return it->second->getPosition();
+    }
+    return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
+}
+
+void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+    if (mShowTouchesEnabled == enabled) {
+        return;
+    }
+    mShowTouchesEnabled = enabled;
+    updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+    if (mStylusPointerIconEnabled == enabled) {
+        return;
+    }
+    mStylusPointerIconEnabled = enabled;
+    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 =
+            [this, displayId]() REQUIRES(mLock) {
+                auto pc = mPolicy.createPointerController(
+                        PointerControllerInterface::ControllerType::MOUSE);
+                if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+                    pc->setDisplayViewport(*viewport);
+                }
+                return pc;
+            };
+    return ConstructorDelegate(std::move(ctor));
+}
+
+PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
+        int32_t displayId) {
+    std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
+            [this, displayId]() REQUIRES(mLock) {
+                auto pc = mPolicy.createPointerController(
+                        PointerControllerInterface::ControllerType::STYLUS);
+                if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+                    pc->setDisplayViewport(*viewport);
+                }
+                return pc;
+            };
+    return ConstructorDelegate(std::move(ctor));
 }
 
 } // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 5e5f782..f46419e 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -20,8 +20,27 @@
 #include "NotifyArgs.h"
 #include "PointerChoreographerPolicyInterface.h"
 
+#include <android-base/thread_annotations.h>
+#include <type_traits>
+
 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.
+ */
+template <typename Factory>
+struct ConstructorDelegate {
+    constexpr ConstructorDelegate(Factory&& factory) : mFactory(std::move(factory)) {}
+
+    using ConstructedType = std::invoke_result_t<const Factory&>;
+    constexpr operator ConstructedType() const { return mFactory(); }
+
+    Factory mFactory;
+};
+
 /**
  * PointerChoreographer manages the icons shown by the system for input interactions.
  * This includes showing the mouse cursor, stylus hover icons, and touch spots.
@@ -31,6 +50,25 @@
 class PointerChoreographerInterface : public InputListenerInterface {
 public:
     /**
+     * Set the display that pointers, like the mouse cursor and drawing tablets,
+     * should be drawn on.
+     */
+    virtual void setDefaultMouseDisplayId(int32_t displayId) = 0;
+    virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
+    virtual std::optional<DisplayViewport> getViewportForPointerDevice(
+            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
+    virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
+    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;
@@ -42,6 +80,16 @@
                                   PointerChoreographerPolicyInterface&);
     ~PointerChoreographer() override = default;
 
+    void setDefaultMouseDisplayId(int32_t displayId) override;
+    void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
+    std::optional<DisplayViewport> getViewportForPointerDevice(
+            int32_t associatedDisplayId) override;
+    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;
     void notifyKey(const NotifyKeyArgs& args) override;
@@ -55,7 +103,46 @@
     void dump(std::string& dump) override;
 
 private:
+    void updatePointerControllersLocked() REQUIRES(mLock);
+    void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
+    const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
+    int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+    std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
+            int32_t associatedDisplayId) REQUIRES(mLock);
+    InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
+
+    NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
+    NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processDeviceReset(const NotifyDeviceResetArgs& args);
+
+    using ControllerConstructor =
+            ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
+    ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
+    ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
+    ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
+
+    std::mutex mLock;
+
     InputListenerInterface& mNextListener;
+    PointerChoreographerPolicyInterface& mPolicy;
+
+    std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
+            GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
+            GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
+            GUARDED_BY(mLock);
+
+    int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
+    int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
+    std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
+    std::set<DeviceId> mMouseDevices GUARDED_BY(mLock);
+    std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
+    bool mShowTouchesEnabled GUARDED_BY(mLock);
+    bool mStylusPointerIconEnabled GUARDED_BY(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
index ee0ab33..d9d0450 100644
--- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -15,10 +15,15 @@
  */
 
 #include "PreferStylusOverTouchBlocker.h"
+#include <com_android_input_flags.h>
 #include <input/PrintTools.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
+const bool BLOCK_TOUCH_WHEN_STYLUS_HOVER = !input_flags::disable_reject_touch_on_stylus_hover();
+
 static std::pair<bool, bool> checkToolType(const NotifyMotionArgs& args) {
     bool hasStylus = false;
     bool hasTouch = false;
@@ -96,8 +101,11 @@
 std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
         const NotifyMotionArgs& args) {
     const auto [hasTouch, hasStylus] = checkToolType(args);
-    const bool isUpOrCancel =
-            args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL;
+    const bool isDisengageOrCancel = BLOCK_TOUCH_WHEN_STYLUS_HOVER
+            ? (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+               args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL)
+            : (args.action == AMOTION_EVENT_ACTION_UP ||
+               args.action == AMOTION_EVENT_ACTION_CANCEL);
 
     if (hasTouch && hasStylus) {
         mDevicesWithMixedToolType.insert(args.deviceId);
@@ -109,7 +117,7 @@
         if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
             // If we started to cancel events from this device, continue to do so to keep
             // the stream consistent. It should happen at most once per "mixed" device.
-            if (isUpOrCancel) {
+            if (isDisengageOrCancel) {
                 mCanceledDevices.erase(args.deviceId);
                 mLastTouchEvents.erase(args.deviceId);
             }
@@ -119,10 +127,13 @@
     }
 
     const bool isStylusEvent = hasStylus;
-    const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
+    const bool isEngage = BLOCK_TOUCH_WHEN_STYLUS_HOVER
+            ? (args.action == AMOTION_EVENT_ACTION_DOWN ||
+               args.action == AMOTION_EVENT_ACTION_HOVER_ENTER)
+            : (args.action == AMOTION_EVENT_ACTION_DOWN);
 
     if (isStylusEvent) {
-        if (isDown) {
+        if (isEngage) {
             // Reject all touch while stylus is down
             mActiveStyli.insert(args.deviceId);
 
@@ -143,7 +154,7 @@
             result.push_back(args);
             return result;
         }
-        if (isUpOrCancel) {
+        if (isDisengageOrCancel) {
             mActiveStyli.erase(args.deviceId);
         }
         // Never drop stylus events
@@ -158,7 +169,7 @@
         }
 
         const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
-        if (isUpOrCancel) {
+        if (isDisengageOrCancel) {
             mCanceledDevices.erase(args.deviceId);
             mLastTouchEvents.erase(args.deviceId);
         }
@@ -169,7 +180,7 @@
             return {};
         }
 
-        if (!isUpOrCancel) {
+        if (!isDisengageOrCancel) {
             mLastTouchEvents[args.deviceId] = args;
         }
         return {args};
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 6f092a6..513cbfa 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -148,7 +148,7 @@
       ]
     }
   ],
-  "hwasan-postsubmit": [
+  "postsubmit": [
     {
       "name": "CtsWindowManagerDeviceWindow",
       "options": [
@@ -281,6 +281,9 @@
           "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
         }
       ]
+    },
+    {
+      "name": "CtsInputHostTestCases"
     }
   ]
 }
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index 0f62324..1e2b9b3a 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -67,6 +67,16 @@
 const bool DEBUG_MODEL =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
 
+/**
+ * When multi-device input is enabled, we shouldn't use PreferStylusOverTouchBlocker at all.
+ * However, multi-device input has the following default behaviour: hovering stylus rejects touch.
+ * Therefore, if we want to disable that behaviour (and go back to a place where stylus down
+ * blocks touch, but hovering stylus doesn't interact with touch), we should just disable the entire
+ * multi-device input feature.
+ */
+const bool ENABLE_MULTI_DEVICE_INPUT = input_flags::enable_multi_device_input() &&
+        !input_flags::disable_reject_touch_on_stylus_hover();
+
 // Category (=namespace) name for the input settings that are applied at boot time
 static const char* INPUT_NATIVE_BOOT = "input_native_boot";
 /**
@@ -347,7 +357,7 @@
     ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
-        if (input_flags::enable_multi_device_input()) {
+        if (ENABLE_MULTI_DEVICE_INPUT) {
             notifyMotionLocked(args);
         } else {
             const std::vector<NotifyMotionArgs> processedArgs =
diff --git a/services/inputflinger/aidl/Android.bp b/services/inputflinger/aidl/Android.bp
index 314c433..d068129 100644
--- a/services/inputflinger/aidl/Android.bp
+++ b/services/inputflinger/aidl/Android.bp
@@ -17,6 +17,9 @@
     srcs: ["**/*.aidl"],
     unstable: true,
     host_supported: true,
+    imports: [
+        "android.hardware.input.common-V1",
+    ],
     backend: {
         cpp: {
             enabled: false,
diff --git a/libs/gui/include/gui/Flags.h b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
similarity index 73%
copy from libs/gui/include/gui/Flags.h
copy to services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
index a2cff56..b9e6a03 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-#pragma once
+package com.android.server.inputflinger;
 
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+/**
+ * Analogous to Android's InputDeviceInfo
+ * Stores the basic information connected input devices.
+ */
+parcelable DeviceInfo {
+    int deviceId;
+    boolean external;
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
new file mode 100644
index 0000000..2921d30
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.InputFilterConfiguration;
+import com.android.server.inputflinger.KeyEvent;
+
+/**
+ * A local AIDL interface used as a foreign function interface (ffi) to
+ * filter input events.
+ *
+ * NOTE: Since we use this as a local interface, all processing happens on the
+ * calling thread.
+ */
+interface IInputFilter {
+
+    /** Callbacks for the rust InputFilter to call into C++. */
+    interface IInputFilterCallbacks {
+        /** Sends back a filtered key event */
+        void sendKeyEvent(in KeyEvent event);
+
+        /** Sends back modifier state */
+        void onModifierStateChanged(int modifierState, int lockedModifierState);
+    }
+
+    /** Returns if InputFilter is enabled */
+    boolean isEnabled();
+
+    /** Notifies if a key event occurred */
+    void notifyKey(in KeyEvent event);
+
+    /** Notifies if any InputDevice list changed and provides the list of connected peripherals */
+    void notifyInputDevicesChanged(in DeviceInfo[] deviceInfos);
+
+    /** Notifies when configuration changes */
+    void notifyConfigurationChanged(in InputFilterConfiguration config);
+}
+
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
index 8e826fd..de852c0 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
@@ -16,6 +16,9 @@
 
 package com.android.server.inputflinger;
 
+import com.android.server.inputflinger.IInputFilter;
+import com.android.server.inputflinger.IInputFilter.IInputFilterCallbacks;
+
 /**
  * A local AIDL interface used as a foreign function interface (ffi) to
  * communicate with the Rust component of inputflinger.
@@ -31,4 +34,7 @@
     interface IInputFlingerRustBootstrapCallback {
         void onProvideInputFlingerRust(in IInputFlingerRust inputFlingerRust);
     }
+
+    /** Create the rust implementation of InputFilter. */
+    IInputFilter createInputFilter(IInputFilterCallbacks callbacks);
 }
diff --git a/libs/gui/include/gui/Flags.h b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
similarity index 64%
copy from libs/gui/include/gui/Flags.h
copy to services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index a2cff56..38b1612 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
-#pragma once
+package com.android.server.inputflinger;
 
-// TODO(281695725): replace this with build time flags, whenever they are available
-#ifndef FLAG_BQ_SET_FRAME_RATE
-#define FLAG_BQ_SET_FRAME_RATE false
-#endif
\ No newline at end of file
+/**
+ * Contains data for the current Input filter configuration
+ */
+parcelable InputFilterConfiguration {
+    // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
+    long bounceKeysThresholdNs;
+    // If sticky keys filter is enabled
+    boolean stickyKeysEnabled;
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
new file mode 100644
index 0000000..2cae6e1
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+import android.hardware.input.common.Source;
+import com.android.server.inputflinger.KeyEventAction;
+
+/**
+ * Analogous to Android's native KeyEvent / NotifyKeyArgs.
+ * Stores the basic information about Key events.
+ */
+@RustDerive(Copy=true, Clone=true, Eq=true, PartialEq=true)
+parcelable KeyEvent {
+    int id;
+    int deviceId;
+    long downTime;
+    long readTime;
+    long eventTime;
+    Source source;
+    int displayId;
+    int policyFlags;
+    KeyEventAction action;
+    int flags;
+    int keyCode;
+    int scanCode;
+    int metaState;
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
new file mode 100644
index 0000000..43ee5fe
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/** Different Key event actions */
+enum KeyEventAction {
+    /** The key has been pressed down. */
+    DOWN = 0,
+
+    /** The key has been released. */
+    UP = 1,
+
+    /**
+     * Multiple duplicate key events have occurred in a row, or a
+     * complex string is being delivered.  The repeat_count property
+     * of the key event contains the number of times the given key
+     * code should be executed.
+     *
+     * NOTE: This is deprecated and should never be used. This just
+     * for consistency with KeyEvent actions defined in NotifyKeyArgs.
+     */
+    MULTIPLE = 2
+}
\ No newline at end of file
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 188d5f0..5ae3715 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -20,10 +20,12 @@
 #include <binder/Binder.h>
 #include <gui/constants.h>
 #include "../dispatcher/InputDispatcher.h"
+#include "../tests/FakeApplicationHandle.h"
+#include "../tests/FakeInputDispatcherPolicy.h"
+#include "../tests/FakeWindowHandle.h"
 
 using android::base::Result;
 using android::gui::WindowInfo;
-using android::gui::WindowInfoHandle;
 using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
@@ -33,171 +35,17 @@
 namespace {
 
 // An arbitrary device id.
-constexpr int32_t DEVICE_ID = 1;
+constexpr DeviceId DEVICE_ID = 1;
 
-// The default pid and uid for windows created by the test.
-constexpr gui::Pid WINDOW_PID{999};
-constexpr gui::Uid WINDOW_UID{1001};
+// An arbitrary display id
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
 
 static nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-// --- FakeInputDispatcherPolicy ---
-
-class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-public:
-    FakeInputDispatcherPolicy() = default;
-    virtual ~FakeInputDispatcherPolicy() = default;
-
-private:
-    void notifyConfigurationChanged(nsecs_t) override {}
-
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        ALOGE("There is no focused window for %s", applicationHandle->getName().c_str());
-    }
-
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string& reason) override {
-        ALOGE("Window is not responding: %s", reason.c_str());
-    }
-
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {}
-
-    void notifyInputChannelBroken(const sp<IBinder>&) override {}
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
-    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
-                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
-
-    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        return true; // dispatch event normally
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
-
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        return 0;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
-                                                 uint32_t) override {
-        return {};
-    }
-
-    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
-
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
-
-    void setPointerCapture(const PointerCaptureRequest&) override {}
-
-    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
-
-    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {}
-
-    InputDispatcherConfiguration mConfig;
-};
-
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
-    FakeApplicationHandle() {}
-    virtual ~FakeApplicationHandle() {}
-
-    virtual bool updateInfo() {
-        mInfo.dispatchingTimeoutMillis =
-                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
-        return true;
-    }
-};
-
-class FakeInputReceiver {
-public:
-    void consumeEvent() {
-        uint32_t consumeSeq = 0;
-        InputEvent* event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t result = WOULD_BLOCK;
-        while (result == WOULD_BLOCK) {
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > 10ms) {
-                ALOGE("Waited too long for consumer to produce an event, giving up");
-                break;
-            }
-            result = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                        &event);
-        }
-        if (result != OK) {
-            ALOGE("Received result = %d from consume()", result);
-        }
-        result = mConsumer->sendFinishedSignal(consumeSeq, true);
-        if (result != OK) {
-            ALOGE("Received result = %d from sendFinishedSignal", result);
-        }
-    }
-
-protected:
-    explicit FakeInputReceiver(InputDispatcher& dispatcher, const std::string name) {
-        Result<std::unique_ptr<InputChannel>> channelResult = dispatcher.createInputChannel(name);
-        LOG_ALWAYS_FATAL_IF(!channelResult.ok());
-        mClientChannel = std::move(*channelResult);
-        mConsumer = std::make_unique<InputConsumer>(mClientChannel);
-    }
-
-    virtual ~FakeInputReceiver() {}
-
-    std::shared_ptr<InputChannel> mClientChannel;
-    std::unique_ptr<InputConsumer> mConsumer;
-    PreallocatedInputEventFactory mEventFactory;
-};
-
-class FakeWindowHandle : public WindowInfoHandle, public FakeInputReceiver {
-public:
-    static const int32_t WIDTH = 200;
-    static const int32_t HEIGHT = 200;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     InputDispatcher& dispatcher, const std::string name)
-          : FakeInputReceiver(dispatcher, name), mFrame(Rect(0, 0, WIDTH, HEIGHT)) {
-        inputApplicationHandle->updateInfo();
-        updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-    }
-
-    void updateInfo() {
-        mInfo.token = mClientChannel->getConnectionToken();
-        mInfo.name = "FakeWindowHandle";
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.frame = mFrame;
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(mFrame);
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = ADISPLAY_ID_DEFAULT;
-    }
-
-protected:
-    Rect mFrame;
-};
-
 static MotionEvent generateMotionEvent() {
     PointerProperties pointerProperties[1];
     PointerCoords pointerCoords[1];
@@ -263,7 +111,7 @@
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -281,8 +129,8 @@
         motionArgs.eventTime = now();
         dispatcher.notifyMotion(motionArgs);
 
-        window->consumeEvent();
-        window->consumeEvent();
+        window->consumeMotion();
+        window->consumeMotion();
     }
 
     dispatcher.stop();
@@ -298,7 +146,7 @@
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -315,8 +163,8 @@
                                     INJECT_EVENT_TIMEOUT,
                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
-        window->consumeEvent();
-        window->consumeEvent();
+        window->consumeMotion();
+        window->consumeMotion();
     }
 
     dispatcher.stop();
@@ -332,7 +180,7 @@
     // Create a window
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     std::vector<gui::WindowInfo> windowInfos{*window->getInfo()};
     gui::DisplayInfo info;
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index c7d98ab..c889b9b 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -69,6 +69,16 @@
         android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Injection");
 
 /**
+ * Generally, we always log whenever events are dropped. However, to reduce logspam, some messages
+ * are suppressed.
+ * Log additional debug messages about dropped input events with this flag.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherDroppedEventsVerbose DEBUG".
+ * Requires system_server restart via `adb shell stop && adb shell start`.
+ */
+const bool DEBUG_DROPPED_EVENTS_VERBOSE =
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "DroppedEventsVerbose");
+
+/**
  * Log debug messages about input focus tracking.
  * Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
  */
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 30e6802..cc0d49c 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -67,24 +67,11 @@
         injectionState(nullptr),
         dispatchInProgress(false) {}
 
-EventEntry::~EventEntry() {
-    releaseInjectionState();
-}
-
-void EventEntry::releaseInjectionState() {
-    if (injectionState) {
-        injectionState->release();
-        injectionState = nullptr;
-    }
-}
-
 // --- ConfigurationChangedEntry ---
 
 ConfigurationChangedEntry::ConfigurationChangedEntry(int32_t id, nsecs_t eventTime)
       : EventEntry(id, Type::CONFIGURATION_CHANGED, eventTime, 0) {}
 
-ConfigurationChangedEntry::~ConfigurationChangedEntry() {}
-
 std::string ConfigurationChangedEntry::getDescription() const {
     return StringPrintf("ConfigurationChangedEvent(), policyFlags=0x%08x", policyFlags);
 }
@@ -94,8 +81,6 @@
 DeviceResetEntry::DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId)
       : EventEntry(id, Type::DEVICE_RESET, eventTime, 0), deviceId(deviceId) {}
 
-DeviceResetEntry::~DeviceResetEntry() {}
-
 std::string DeviceResetEntry::getDescription() const {
     return StringPrintf("DeviceResetEvent(deviceId=%d), policyFlags=0x%08x", deviceId, policyFlags);
 }
@@ -110,8 +95,6 @@
         hasFocus(hasFocus),
         reason(reason) {}
 
-FocusEntry::~FocusEntry() {}
-
 std::string FocusEntry::getDescription() const {
     return StringPrintf("FocusEvent(hasFocus=%s)", hasFocus ? "true" : "false");
 }
@@ -125,8 +108,6 @@
       : EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
         pointerCaptureRequest(request) {}
 
-PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
-
 std::string PointerCaptureChangedEntry::getDescription() const {
     return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
                         pointerCaptureRequest.enable ? "true" : "false");
@@ -143,34 +124,32 @@
         x(x),
         y(y) {}
 
-DragEntry::~DragEntry() {}
-
 std::string DragEntry::getDescription() const {
     return StringPrintf("DragEntry(isExiting=%s, x=%f, y=%f)", isExiting ? "true" : "false", x, y);
 }
 
 // --- KeyEntry ---
 
-KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                   int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags,
-                   int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount,
-                   nsecs_t downTime)
+KeyEntry::KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+                   int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+                   int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                   int32_t metaState, int32_t repeatCount, nsecs_t downTime)
       : EventEntry(id, Type::KEY, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
         displayId(displayId),
         action(action),
-        flags(flags),
         keyCode(keyCode),
         scanCode(scanCode),
         metaState(metaState),
-        repeatCount(repeatCount),
         downTime(downTime),
         syntheticRepeat(false),
         interceptKeyResult(KeyEntry::InterceptKeyResult::UNKNOWN),
-        interceptKeyWakeupTime(0) {}
-
-KeyEntry::~KeyEntry() {}
+        interceptKeyWakeupTime(0),
+        flags(flags),
+        repeatCount(repeatCount) {
+    EventEntry::injectionState = std::move(injectionState);
+}
 
 std::string KeyEntry::getDescription() const {
     if (!IS_DEBUGGABLE_BUILD) {
@@ -185,15 +164,6 @@
                         keyCode, scanCode, metaState, repeatCount, policyFlags);
 }
 
-void KeyEntry::recycle() {
-    releaseInjectionState();
-
-    dispatchInProgress = false;
-    syntheticRepeat = false;
-    interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
-    interceptKeyWakeupTime = 0;
-}
-
 // --- TouchModeEntry ---
 
 TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId)
@@ -201,21 +171,19 @@
         inTouchMode(inTouchMode),
         displayId(displayId) {}
 
-TouchModeEntry::~TouchModeEntry() {}
-
 std::string TouchModeEntry::getDescription() const {
     return StringPrintf("TouchModeEvent(inTouchMode=%s)", inTouchMode ? "true" : "false");
 }
 
 // --- MotionEntry ---
 
-MotionEntry::MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                         int32_t displayId, uint32_t policyFlags, int32_t action,
-                         int32_t actionButton, int32_t flags, int32_t metaState,
-                         int32_t buttonState, MotionClassification classification,
-                         int32_t edgeFlags, float xPrecision, float yPrecision,
-                         float xCursorPosition, float yCursorPosition, nsecs_t downTime,
-                         const std::vector<PointerProperties>& pointerProperties,
+MotionEntry::MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState,
+                         nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+                         uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+                         int32_t metaState, int32_t buttonState,
+                         MotionClassification classification, int32_t edgeFlags, float xPrecision,
+                         float yPrecision, float xCursorPosition, float yCursorPosition,
+                         nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
                          const std::vector<PointerCoords>& pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
@@ -234,9 +202,9 @@
         yCursorPosition(yCursorPosition),
         downTime(downTime),
         pointerProperties(pointerProperties),
-        pointerCoords(pointerCoords) {}
-
-MotionEntry::~MotionEntry() {}
+        pointerCoords(pointerCoords) {
+    EventEntry::injectionState = std::move(injectionState);
+}
 
 std::string MotionEntry::getDescription() const {
     if (!IS_DEBUGGABLE_BUILD) {
@@ -285,8 +253,6 @@
         hwTimestamp(hwTimestamp),
         values(std::move(values)) {}
 
-SensorEntry::~SensorEntry() {}
-
 std::string SensorEntry::getDescription() const {
     std::string msg;
     msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
@@ -310,7 +276,7 @@
 
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
-DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
+DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
                              ftl::Flags<InputTarget::Flags> targetFlags,
                              const ui::Transform& transform, const ui::Transform& rawTransform,
                              float globalScaleFactor)
@@ -321,21 +287,15 @@
         rawTransform(rawTransform),
         globalScaleFactor(globalScaleFactor),
         deliveryTime(0),
-        resolvedAction(0),
         resolvedFlags(0) {
     switch (this->eventEntry->type) {
         case EventEntry::Type::KEY: {
-            const KeyEntry& keyEntry = static_cast<KeyEntry&>(*this->eventEntry);
-            resolvedEventId = keyEntry.id;
-            resolvedAction = keyEntry.action;
+            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*this->eventEntry);
             resolvedFlags = keyEntry.flags;
-
             break;
         }
         case EventEntry::Type::MOTION: {
-            const MotionEntry& motionEntry = static_cast<MotionEntry&>(*this->eventEntry);
-            resolvedEventId = motionEntry.id;
-            resolvedAction = motionEntry.action;
+            const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*this->eventEntry);
             resolvedFlags = motionEntry.flags;
             break;
         }
@@ -355,24 +315,9 @@
 }
 
 std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry) {
-    out << "DispatchEntry{resolvedAction=";
-    switch (entry.eventEntry->type) {
-        case EventEntry::Type::KEY: {
-            out << KeyEvent::actionToString(entry.resolvedAction);
-            break;
-        }
-        case EventEntry::Type::MOTION: {
-            out << MotionEvent::actionToString(entry.resolvedAction);
-            break;
-        }
-        default: {
-            out << "<invalid, not a key or a motion>";
-            break;
-        }
-    }
     std::string transform;
     entry.transform.dump(transform, "transform");
-    out << ", resolvedFlags=" << entry.resolvedFlags
+    out << "DispatchEntry{resolvedFlags=" << entry.resolvedFlags
         << ", targetFlags=" << entry.targetFlags.string() << ", transform=" << transform
         << "} original: " << entry.eventEntry->getDescription();
     return out;
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index b341784..e2e13c3 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -48,9 +48,9 @@
     Type type;
     nsecs_t eventTime;
     uint32_t policyFlags;
-    InjectionState* injectionState;
+    std::shared_ptr<InjectionState> injectionState;
 
-    bool dispatchInProgress; // initially false, set to true while dispatching
+    mutable bool dispatchInProgress; // initially false, set to true while dispatching
 
     /**
      * Injected keys are events from an external (probably untrusted) application
@@ -72,17 +72,14 @@
     virtual std::string getDescription() const = 0;
 
     EventEntry(int32_t id, Type type, nsecs_t eventTime, uint32_t policyFlags);
-    virtual ~EventEntry();
-
-protected:
-    void releaseInjectionState();
+    EventEntry(const EventEntry&) = delete;
+    EventEntry& operator=(const EventEntry&) = delete;
+    virtual ~EventEntry() = default;
 };
 
 struct ConfigurationChangedEntry : EventEntry {
     explicit ConfigurationChangedEntry(int32_t id, nsecs_t eventTime);
     std::string getDescription() const override;
-
-    ~ConfigurationChangedEntry() override;
 };
 
 struct DeviceResetEntry : EventEntry {
@@ -90,8 +87,6 @@
 
     DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId);
     std::string getDescription() const override;
-
-    ~DeviceResetEntry() override;
 };
 
 struct FocusEntry : EventEntry {
@@ -102,8 +97,6 @@
     FocusEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool hasFocus,
                const std::string& reason);
     std::string getDescription() const override;
-
-    ~FocusEntry() override;
 };
 
 struct PointerCaptureChangedEntry : EventEntry {
@@ -111,8 +104,6 @@
 
     PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&);
     std::string getDescription() const override;
-
-    ~PointerCaptureChangedEntry() override;
 };
 
 struct DragEntry : EventEntry {
@@ -123,8 +114,6 @@
     DragEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool isExiting, float x,
               float y);
     std::string getDescription() const override;
-
-    ~DragEntry() override;
 };
 
 struct KeyEntry : EventEntry {
@@ -132,11 +121,9 @@
     uint32_t source;
     int32_t displayId;
     int32_t action;
-    int32_t flags;
     int32_t keyCode;
     int32_t scanCode;
     int32_t metaState;
-    int32_t repeatCount;
     nsecs_t downTime;
 
     bool syntheticRepeat; // set to true for synthetic key repeats
@@ -147,16 +134,17 @@
         CONTINUE,
         TRY_AGAIN_LATER,
     };
-    InterceptKeyResult interceptKeyResult; // set based on the interception result
-    nsecs_t interceptKeyWakeupTime;        // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+    // These are special fields that may need to be modified while the event is being dispatched.
+    mutable InterceptKeyResult interceptKeyResult; // set based on the interception result
+    mutable nsecs_t interceptKeyWakeupTime;        // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+    mutable int32_t flags;
+    mutable int32_t repeatCount;
 
-    KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
-             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
-             int32_t metaState, int32_t repeatCount, nsecs_t downTime);
+    KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+             int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+             int32_t repeatCount, nsecs_t downTime);
     std::string getDescription() const override;
-    void recycle();
-
-    ~KeyEntry() override;
 };
 
 struct MotionEntry : EventEntry {
@@ -180,16 +168,14 @@
 
     size_t getPointerCount() const { return pointerProperties.size(); }
 
-    MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
-                uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
-                int32_t metaState, int32_t buttonState, MotionClassification classification,
-                int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
-                float yCursorPosition, nsecs_t downTime,
-                const std::vector<PointerProperties>& pointerProperties,
+    MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+                int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+                int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
+                int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
+                float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
+                nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
                 const std::vector<PointerCoords>& pointerCoords);
     std::string getDescription() const override;
-
-    ~MotionEntry() override;
 };
 
 std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry);
@@ -209,8 +195,6 @@
                 InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
                 std::vector<float> values);
     std::string getDescription() const override;
-
-    ~SensorEntry() override;
 };
 
 struct TouchModeEntry : EventEntry {
@@ -219,15 +203,13 @@
 
     TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId);
     std::string getDescription() const override;
-
-    ~TouchModeEntry() override;
 };
 
 // Tracks the progress of dispatching a particular event to a particular connection.
 struct DispatchEntry {
     const uint32_t seq; // unique sequence number, never 0
 
-    std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
+    std::shared_ptr<const EventEntry> eventEntry; // the event to dispatch
     const ftl::Flags<InputTarget::Flags> targetFlags;
     ui::Transform transform;
     ui::Transform rawTransform;
@@ -238,12 +220,9 @@
     // An ANR will be triggered if a response for this entry is not received by timeoutTime
     nsecs_t timeoutTime;
 
-    // Set to the resolved ID, action and flags when the event is enqueued.
-    int32_t resolvedEventId;
-    int32_t resolvedAction;
     int32_t resolvedFlags;
 
-    DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
+    DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
                   ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
                   const ui::Transform& rawTransform, float globalScaleFactor);
     DispatchEntry(const DispatchEntry&) = delete;
diff --git a/services/inputflinger/dispatcher/InjectionState.cpp b/services/inputflinger/dispatcher/InjectionState.cpp
index 053594b..f693dcf 100644
--- a/services/inputflinger/dispatcher/InjectionState.cpp
+++ b/services/inputflinger/dispatcher/InjectionState.cpp
@@ -20,22 +20,10 @@
 
 namespace android::inputdispatcher {
 
-InjectionState::InjectionState(const std::optional<gui::Uid>& targetUid)
-      : refCount(1),
-        targetUid(targetUid),
+InjectionState::InjectionState(const std::optional<gui::Uid>& targetUid, bool isAsync)
+      : targetUid(targetUid),
+        injectionIsAsync(isAsync),
         injectionResult(android::os::InputEventInjectionResult::PENDING),
-        injectionIsAsync(false),
         pendingForegroundDispatches(0) {}
 
-InjectionState::~InjectionState() {}
-
-void InjectionState::release() {
-    refCount -= 1;
-    if (refCount == 0) {
-        delete this;
-    } else {
-        ALOG_ASSERT(refCount > 0);
-    }
-}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InjectionState.h b/services/inputflinger/dispatcher/InjectionState.h
index 3a3f5ae..8225dec 100644
--- a/services/inputflinger/dispatcher/InjectionState.h
+++ b/services/inputflinger/dispatcher/InjectionState.h
@@ -24,18 +24,12 @@
 namespace inputdispatcher {
 
 struct InjectionState {
-    mutable int32_t refCount;
-
-    std::optional<gui::Uid> targetUid;
+    const std::optional<gui::Uid> targetUid;
+    const bool injectionIsAsync; // set to true if injection is not waiting for the result
     android::os::InputEventInjectionResult injectionResult; // initially PENDING
-    bool injectionIsAsync;               // set to true if injection is not waiting for the result
     int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
 
-    explicit InjectionState(const std::optional<gui::Uid>& targetUid);
-    void release();
-
-private:
-    ~InjectionState();
+    explicit InjectionState(const std::optional<gui::Uid>& targetUid, bool isAsync);
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 7dfbf94..162a7bd 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -72,6 +72,9 @@
 using android::os::InputEventInjectionSync;
 namespace input_flags = com::android::input::flags;
 
+// TODO(b/312714754): remove the corresponding code, as well.
+static const bool REMOVE_APP_SWITCH_DROPS = true;
+
 namespace android::inputdispatcher {
 
 namespace {
@@ -124,10 +127,6 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-bool isEmpty(const std::stringstream& ss) {
-    return ss.rdbuf()->in_avail() == 0;
-}
-
 inline const std::string binderToString(const sp<IBinder>& binder) {
     if (binder == nullptr) {
         return "<null>";
@@ -254,6 +253,14 @@
     }
 }
 
+std::bitset<MAX_POINTER_ID + 1> getPointerIds(const std::vector<PointerProperties>& pointers) {
+    std::bitset<MAX_POINTER_ID + 1> pointerIds;
+    for (const PointerProperties& pointer : pointers) {
+        pointerIds.set(pointer.id);
+    }
+    return pointerIds;
+}
+
 std::string dumpRegion(const Region& region) {
     if (region.isEmpty()) {
         return "<empty>";
@@ -293,9 +300,8 @@
         }
         dump.append(INDENT4);
         dump += entry.eventEntry->getDescription();
-        dump += StringPrintf(", seq=%" PRIu32 ", targetFlags=%s, resolvedAction=%d, age=%" PRId64
-                             "ms",
-                             entry.seq, entry.targetFlags.string().c_str(), entry.resolvedAction,
+        dump += StringPrintf(", seq=%" PRIu32 ", targetFlags=%s, age=%" PRId64 "ms", entry.seq,
+                             entry.targetFlags.string().c_str(),
                              ns2ms(currentTime - entry.eventEntry->eventTime));
         if (entry.deliveryTime != 0) {
             // This entry was delivered, so add information on how long we've been waiting
@@ -352,7 +358,7 @@
 }
 
 std::unique_ptr<DispatchEntry> createDispatchEntry(
-        const InputTarget& inputTarget, std::shared_ptr<EventEntry> eventEntry,
+        const InputTarget& inputTarget, std::shared_ptr<const EventEntry> eventEntry,
         ftl::Flags<InputTarget::Flags> inputTargetFlags) {
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
@@ -390,21 +396,17 @@
     }
 
     std::unique_ptr<MotionEntry> combinedMotionEntry =
-            std::make_unique<MotionEntry>(motionEntry.id, motionEntry.eventTime,
-                                          motionEntry.deviceId, motionEntry.source,
-                                          motionEntry.displayId, motionEntry.policyFlags,
-                                          motionEntry.action, motionEntry.actionButton,
-                                          motionEntry.flags, motionEntry.metaState,
-                                          motionEntry.buttonState, motionEntry.classification,
-                                          motionEntry.edgeFlags, motionEntry.xPrecision,
-                                          motionEntry.yPrecision, motionEntry.xCursorPosition,
-                                          motionEntry.yCursorPosition, motionEntry.downTime,
-                                          motionEntry.pointerProperties, pointerCoords);
-
-    if (motionEntry.injectionState) {
-        combinedMotionEntry->injectionState = motionEntry.injectionState;
-        combinedMotionEntry->injectionState->refCount += 1;
-    }
+            std::make_unique<MotionEntry>(motionEntry.id, motionEntry.injectionState,
+                                          motionEntry.eventTime, motionEntry.deviceId,
+                                          motionEntry.source, motionEntry.displayId,
+                                          motionEntry.policyFlags, motionEntry.action,
+                                          motionEntry.actionButton, motionEntry.flags,
+                                          motionEntry.metaState, motionEntry.buttonState,
+                                          motionEntry.classification, motionEntry.edgeFlags,
+                                          motionEntry.xPrecision, motionEntry.yPrecision,
+                                          motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+                                          motionEntry.downTime, motionEntry.pointerProperties,
+                                          pointerCoords);
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
@@ -457,10 +459,6 @@
 bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry, const Connection& connection) {
     const EventEntry& eventEntry = *dispatchEntry.eventEntry;
     const int32_t& inputEventId = eventEntry.id;
-    if (inputEventId != dispatchEntry.resolvedEventId) {
-        // Event was transmuted
-        return false;
-    }
     if (inputEventId == android::os::IInputConstants::INVALID_INPUT_EVENT_ID) {
         return false;
     }
@@ -641,22 +639,22 @@
     }
 
     // We should consider all hovering pointers here. But for now, just use the first one
-    const int32_t pointerId = entry.pointerProperties[0].id;
+    const PointerProperties& pointer = entry.pointerProperties[0];
 
     std::set<sp<WindowInfoHandle>> oldWindows;
     if (oldState != nullptr) {
-        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
     }
 
     std::set<sp<WindowInfoHandle>> newWindows =
-            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointer.id);
 
     // If the pointer is no longer in the new window set, send HOVER_EXIT.
     for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
         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);
         }
     }
@@ -667,7 +665,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)) {
@@ -681,9 +679,9 @@
                 }
                 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);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -751,15 +749,25 @@
     return true;
 }
 
+/**
+ * Return true if stylus is currently down anywhere on the specified display, and false otherwise.
+ */
+bool isStylusActiveInDisplay(
+        int32_t displayId,
+        const std::unordered_map<int32_t /*displayId*/, TouchState>& touchStatesByDisplay) {
+    const auto it = touchStatesByDisplay.find(displayId);
+    if (it == touchStatesByDisplay.end()) {
+        return false;
+    }
+    const TouchState& state = it->second;
+    return state.hasActiveStylus();
+}
+
 } // namespace
 
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
-      : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {}
-
-InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
-                                 std::chrono::nanoseconds staleEventTimeout)
       : mPolicy(policy),
         mPendingEvent(nullptr),
         mLastDropReason(DropReason::NOT_DROPPED),
@@ -774,7 +782,6 @@
         mMaximumObscuringOpacityForTouch(1.0f),
         mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
-        mStaleEventTimeout(staleEventTimeout),
         mLatencyAggregator(),
         mLatencyTracker(&mLatencyAggregator) {
     mLooper = sp<Looper>::make(false);
@@ -828,7 +835,7 @@
         // Run a dispatch loop if there are no pending commands.
         // The dispatch loop might enqueue commands to run afterwards.
         if (!haveCommandsLocked()) {
-            dispatchOnceInnerLocked(&nextWakeupTime);
+            dispatchOnceInnerLocked(/*byref*/ nextWakeupTime);
         }
 
         // Run all pending commands if there are any.
@@ -935,7 +942,7 @@
     return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
 }
 
-void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) {
     nsecs_t currentTime = now();
 
     // Reset the key repeat timer whenever normal dispatch is suspended while the
@@ -956,20 +963,23 @@
     // Optimize latency of app switches.
     // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
     // been pressed.  When it expires, we preempt dispatch and drop all other pending events.
-    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
-    if (mAppSwitchDueTime < *nextWakeupTime) {
-        *nextWakeupTime = mAppSwitchDueTime;
+    bool isAppSwitchDue;
+    if (!REMOVE_APP_SWITCH_DROPS) {
+        isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+        nextWakeupTime = std::min(nextWakeupTime, mAppSwitchDueTime);
     }
 
     // Ready to start a new event.
     // If we don't already have a pending event, go grab one.
     if (!mPendingEvent) {
         if (mInboundQueue.empty()) {
-            if (isAppSwitchDue) {
-                // The inbound queue is empty so the app switch key we were waiting
-                // for will never arrive.  Stop waiting for it.
-                resetPendingAppSwitchLocked(false);
-                isAppSwitchDue = false;
+            if (!REMOVE_APP_SWITCH_DROPS) {
+                if (isAppSwitchDue) {
+                    // The inbound queue is empty so the app switch key we were waiting
+                    // for will never arrive.  Stop waiting for it.
+                    resetPendingAppSwitchLocked(false);
+                    isAppSwitchDue = false;
+                }
             }
 
             // Synthesize a key repeat if appropriate.
@@ -977,9 +987,7 @@
                 if (currentTime >= mKeyRepeatState.nextRepeatTime) {
                     mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
                 } else {
-                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
-                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
-                    }
+                    nextWakeupTime = std::min(nextWakeupTime, mKeyRepeatState.nextRepeatTime);
                 }
             }
 
@@ -1033,8 +1041,8 @@
         }
 
         case EventEntry::Type::FOCUS: {
-            std::shared_ptr<FocusEntry> typedEntry =
-                    std::static_pointer_cast<FocusEntry>(mPendingEvent);
+            std::shared_ptr<const FocusEntry> typedEntry =
+                    std::static_pointer_cast<const FocusEntry>(mPendingEvent);
             dispatchFocusLocked(currentTime, typedEntry);
             done = true;
             dropReason = DropReason::NOT_DROPPED; // focus events are never dropped
@@ -1042,7 +1050,7 @@
         }
 
         case EventEntry::Type::TOUCH_MODE_CHANGED: {
-            const auto typedEntry = std::static_pointer_cast<TouchModeEntry>(mPendingEvent);
+            const auto typedEntry = std::static_pointer_cast<const TouchModeEntry>(mPendingEvent);
             dispatchTouchModeChangeLocked(currentTime, typedEntry);
             done = true;
             dropReason = DropReason::NOT_DROPPED; // touch mode events are never dropped
@@ -1051,28 +1059,31 @@
 
         case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
             const auto typedEntry =
-                    std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
+                    std::static_pointer_cast<const PointerCaptureChangedEntry>(mPendingEvent);
             dispatchPointerCaptureChangedLocked(currentTime, typedEntry, dropReason);
             done = true;
             break;
         }
 
         case EventEntry::Type::DRAG: {
-            std::shared_ptr<DragEntry> typedEntry =
-                    std::static_pointer_cast<DragEntry>(mPendingEvent);
+            std::shared_ptr<const DragEntry> typedEntry =
+                    std::static_pointer_cast<const DragEntry>(mPendingEvent);
             dispatchDragLocked(currentTime, typedEntry);
             done = true;
             break;
         }
 
         case EventEntry::Type::KEY: {
-            std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
-            if (isAppSwitchDue) {
-                if (isAppSwitchKeyEvent(*keyEntry)) {
-                    resetPendingAppSwitchLocked(true);
-                    isAppSwitchDue = false;
-                } else if (dropReason == DropReason::NOT_DROPPED) {
-                    dropReason = DropReason::APP_SWITCH;
+            std::shared_ptr<const KeyEntry> keyEntry =
+                    std::static_pointer_cast<const KeyEntry>(mPendingEvent);
+            if (!REMOVE_APP_SWITCH_DROPS) {
+                if (isAppSwitchDue) {
+                    if (isAppSwitchKeyEvent(*keyEntry)) {
+                        resetPendingAppSwitchLocked(true);
+                        isAppSwitchDue = false;
+                    } else if (dropReason == DropReason::NOT_DROPPED) {
+                        dropReason = DropReason::APP_SWITCH;
+                    }
                 }
             }
             if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
@@ -1086,26 +1097,42 @@
         }
 
         case EventEntry::Type::MOTION: {
-            std::shared_ptr<MotionEntry> motionEntry =
-                    std::static_pointer_cast<MotionEntry>(mPendingEvent);
-            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
-                dropReason = DropReason::APP_SWITCH;
+            std::shared_ptr<const MotionEntry> motionEntry =
+                    std::static_pointer_cast<const MotionEntry>(mPendingEvent);
+            if (!REMOVE_APP_SWITCH_DROPS) {
+                if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
+                    dropReason = DropReason::APP_SWITCH;
+                }
             }
             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;
+                if (!isFromSource(motionEntry->source, AINPUT_SOURCE_CLASS_POINTER)) {
+                    // Only drop events that are focus-dispatched.
+                    dropReason = DropReason::BLOCKED;
+                }
             }
             done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
             break;
         }
 
         case EventEntry::Type::SENSOR: {
-            std::shared_ptr<SensorEntry> sensorEntry =
-                    std::static_pointer_cast<SensorEntry>(mPendingEvent);
-            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
-                dropReason = DropReason::APP_SWITCH;
+            std::shared_ptr<const SensorEntry> sensorEntry =
+                    std::static_pointer_cast<const SensorEntry>(mPendingEvent);
+            if (!REMOVE_APP_SWITCH_DROPS) {
+                if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
+                    dropReason = DropReason::APP_SWITCH;
+                }
             }
             //  Sensor timestamps use SYSTEM_TIME_BOOTTIME time base, so we can't use
             // 'currentTime' here, get SYSTEM_TIME_BOOTTIME instead.
@@ -1126,12 +1153,12 @@
         mLastDropReason = dropReason;
 
         releasePendingEventLocked();
-        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
+        nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
 }
 
 bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
-    return std::chrono::nanoseconds(currentTime - entry.eventTime) >= mStaleEventTimeout;
+    return mPolicy.isStaleEvent(currentTime, entry.eventTime);
 }
 
 /**
@@ -1196,7 +1223,7 @@
 bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
     bool needWake = mInboundQueue.empty();
     mInboundQueue.push_back(std::move(newEntry));
-    EventEntry& entry = *(mInboundQueue.back());
+    const EventEntry& entry = *(mInboundQueue.back());
     traceInboundQueueLengthLocked();
 
     switch (entry.type) {
@@ -1207,27 +1234,29 @@
             // If the application takes too long to catch up then we drop all events preceding
             // the app switch key.
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
-            if (isAppSwitchKeyEvent(keyEntry)) {
-                if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
-                    mAppSwitchSawKeyDown = true;
-                } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
-                    if (mAppSwitchSawKeyDown) {
-                        if (DEBUG_APP_SWITCH) {
-                            ALOGD("App switch is pending!");
+
+            if (!REMOVE_APP_SWITCH_DROPS) {
+                if (isAppSwitchKeyEvent(keyEntry)) {
+                    if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
+                        mAppSwitchSawKeyDown = true;
+                    } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
+                        if (mAppSwitchSawKeyDown) {
+                            if (DEBUG_APP_SWITCH) {
+                                ALOGD("App switch is pending!");
+                            }
+                            mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
+                            mAppSwitchSawKeyDown = false;
+                            needWake = true;
                         }
-                        mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
-                        mAppSwitchSawKeyDown = false;
-                        needWake = true;
                     }
                 }
             }
-
             // If a new up event comes in, and the pending event with same key code has been asked
             // to try again later because of the policy. We have to reset the intercept key wake up
             // time for it may have been handled in the policy and could be dropped.
             if (keyEntry.action == AKEY_EVENT_ACTION_UP && mPendingEvent &&
                 mPendingEvent->type == EventEntry::Type::KEY) {
-                KeyEntry& pendingKey = static_cast<KeyEntry&>(*mPendingEvent);
+                const KeyEntry& pendingKey = static_cast<const KeyEntry&>(*mPendingEvent);
                 if (pendingKey.keyCode == keyEntry.keyCode &&
                     pendingKey.interceptKeyResult ==
                             KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
@@ -1242,7 +1271,7 @@
         case EventEntry::Type::MOTION: {
             LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
                                 "Unexpected untrusted event.");
-            if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(entry))) {
+            if (shouldPruneInboundQueueLocked(static_cast<const MotionEntry&>(entry))) {
                 mNextUnblockedEvent = mInboundQueue.back();
                 needWake = true;
             }
@@ -1266,7 +1295,7 @@
     return needWake;
 }
 
-void InputDispatcher::addRecentEventLocked(std::shared_ptr<EventEntry> entry) {
+void InputDispatcher::addRecentEventLocked(std::shared_ptr<const EventEntry> entry) {
     // Do not store sensor event in recent queue to avoid flooding the queue.
     if (entry->type != EventEntry::Type::SENSOR) {
         mRecentQueue.push_back(entry);
@@ -1314,8 +1343,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);
         }
     }
@@ -1362,8 +1391,9 @@
             reason = "inbound event was dropped because of pending overdue app switch";
             break;
         case DropReason::BLOCKED:
-            ALOGI("Dropped event because the current application is not responding and the user "
-                  "has started interacting with a different application.");
+            LOG(INFO) << "Dropping because the current application is not responding and the user "
+                         "has started interacting with a different application: "
+                      << entry.getDescription();
             reason = "inbound event was dropped because the current application is not responding "
                      "and the user has started interacting with a different application";
             break;
@@ -1384,6 +1414,9 @@
     switch (entry.type) {
         case EventEntry::Type::KEY: {
             CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
+            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+            options.displayId = keyEntry.displayId;
+            options.deviceId = keyEntry.deviceId;
             synthesizeCancelationEventsForAllConnectionsLocked(options);
             break;
         }
@@ -1391,10 +1424,14 @@
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
+                options.displayId = motionEntry.displayId;
+                options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             } else {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                            reason);
+                options.displayId = motionEntry.displayId;
+                options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             }
             break;
@@ -1467,7 +1504,7 @@
 
 void InputDispatcher::drainInboundQueueLocked() {
     while (!mInboundQueue.empty()) {
-        std::shared_ptr<EventEntry> entry = mInboundQueue.front();
+        std::shared_ptr<const EventEntry> entry = mInboundQueue.front();
         mInboundQueue.pop_front();
         releaseInboundEventLocked(entry);
     }
@@ -1481,8 +1518,8 @@
     }
 }
 
-void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<EventEntry> entry) {
-    InjectionState* injectionState = entry->injectionState;
+void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<const EventEntry> entry) {
+    const std::shared_ptr<InjectionState>& injectionState = entry->injectionState;
     if (injectionState && injectionState->injectionResult == InputEventInjectionResult::PENDING) {
         if (DEBUG_DISPATCH_CYCLE) {
             ALOGD("Injected inbound event was dropped.");
@@ -1502,16 +1539,17 @@
 }
 
 std::shared_ptr<KeyEntry> InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) {
-    std::shared_ptr<KeyEntry> entry = mKeyRepeatState.lastKeyEntry;
+    std::shared_ptr<const KeyEntry> entry = mKeyRepeatState.lastKeyEntry;
 
     uint32_t policyFlags = entry->policyFlags &
             (POLICY_FLAG_RAW_MASK | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED);
 
     std::shared_ptr<KeyEntry> newEntry =
-            std::make_unique<KeyEntry>(mIdGenerator.nextId(), currentTime, entry->deviceId,
-                                       entry->source, entry->displayId, policyFlags, entry->action,
-                                       entry->flags, entry->keyCode, entry->scanCode,
-                                       entry->metaState, entry->repeatCount + 1, entry->downTime);
+            std::make_unique<KeyEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                       currentTime, entry->deviceId, entry->source,
+                                       entry->displayId, policyFlags, entry->action, entry->flags,
+                                       entry->keyCode, entry->scanCode, entry->metaState,
+                                       entry->repeatCount + 1, entry->downTime);
 
     newEntry->syntheticRepeat = true;
     mKeyRepeatState.lastKeyEntry = newEntry;
@@ -1575,24 +1613,23 @@
 
     // This event should go to the front of the queue, but behind all other focus events
     // Find the last focus event, and insert right after it
-    std::deque<std::shared_ptr<EventEntry>>::reverse_iterator it =
-            std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),
-                         [](const std::shared_ptr<EventEntry>& event) {
-                             return event->type == EventEntry::Type::FOCUS;
-                         });
+    auto it = std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),
+                           [](const std::shared_ptr<const EventEntry>& event) {
+                               return event->type == EventEntry::Type::FOCUS;
+                           });
 
     // Maintain the order of focus events. Insert the entry after all other focus events.
     mInboundQueue.insert(it.base(), std::move(focusEntry));
 }
 
-void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry) {
+void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime,
+                                          std::shared_ptr<const FocusEntry> entry) {
     std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
     if (channel == nullptr) {
         return; // Window has gone away
     }
     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();
@@ -1602,7 +1639,7 @@
 }
 
 void InputDispatcher::dispatchPointerCaptureChangedLocked(
-        nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+        nsecs_t currentTime, const std::shared_ptr<const PointerCaptureChangedEntry>& entry,
         DropReason& dropReason) {
     dropReason = DropReason::NOT_DROPPED;
 
@@ -1666,15 +1703,14 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 
     dropReason = DropReason::NOT_DROPPED;
 }
 
-void InputDispatcher::dispatchTouchModeChangeLocked(nsecs_t currentTime,
-                                                    const std::shared_ptr<TouchModeEntry>& entry) {
+void InputDispatcher::dispatchTouchModeChangeLocked(
+        nsecs_t currentTime, const std::shared_ptr<const TouchModeEntry>& entry) {
     const std::vector<sp<WindowInfoHandle>>& windowHandles =
             getWindowHandlesLocked(entry->displayId);
     if (windowHandles.empty()) {
@@ -1703,14 +1739,13 @@
         }
         InputTarget target;
         target.inputChannel = channel;
-        target.flags = InputTarget::Flags::DISPATCH_AS_IS;
         inputTargets.push_back(target);
     }
     return inputTargets;
 }
 
-bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
-                                        DropReason* dropReason, nsecs_t* nextWakeupTime) {
+bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
+                                        DropReason* dropReason, nsecs_t& nextWakeupTime) {
     // Preprocessing.
     if (!entry->dispatchInProgress) {
         if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
@@ -1761,9 +1796,7 @@
     // Handle case where the policy asked us to try again later last time.
     if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
         if (currentTime < entry->interceptKeyWakeupTime) {
-            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
-                *nextWakeupTime = entry->interceptKeyWakeupTime;
-            }
+            nextWakeupTime = std::min(nextWakeupTime, entry->interceptKeyWakeupTime);
             return false; // wait until next wakeup
         }
         entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
@@ -1819,9 +1852,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));
@@ -1843,8 +1875,8 @@
 }
 
 void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
-                                           const std::shared_ptr<SensorEntry>& entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+                                           const std::shared_ptr<const SensorEntry>& entry,
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
               "source=0x%x, sensorType=%s",
@@ -1872,7 +1904,7 @@
         std::scoped_lock _l(mLock);
 
         for (auto it = mInboundQueue.begin(); it != mInboundQueue.end(); it++) {
-            std::shared_ptr<EventEntry> entry = *it;
+            std::shared_ptr<const EventEntry> entry = *it;
             if (entry->type == EventEntry::Type::SENSOR) {
                 it = mInboundQueue.erase(it);
                 releaseInboundEventLocked(entry);
@@ -1882,8 +1914,9 @@
     return true;
 }
 
-bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
-                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
+bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime,
+                                           std::shared_ptr<const MotionEntry> entry,
+                                           DropReason* dropReason, nsecs_t& nextWakeupTime) {
     ATRACE_CALL();
     // Preprocessing.
     if (!entry->dispatchInProgress) {
@@ -1925,10 +1958,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) {
@@ -1967,14 +1999,14 @@
     enqueueInboundEventLocked(std::move(dragEntry));
 }
 
-void InputDispatcher::dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<DragEntry> entry) {
+void InputDispatcher::dispatchDragLocked(nsecs_t currentTime,
+                                         std::shared_ptr<const DragEntry> entry) {
     std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
     if (channel == nullptr) {
         return; // Window has gone away
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 }
@@ -2013,7 +2045,7 @@
 }
 
 void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
-                                          std::shared_ptr<EventEntry> eventEntry,
+                                          std::shared_ptr<const EventEntry> eventEntry,
                                           const std::vector<InputTarget>& inputTargets) {
     ATRACE_CALL();
     if (DEBUG_DISPATCH_CYCLE) {
@@ -2032,10 +2064,10 @@
         if (connection != nullptr) {
             prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
         } else {
-            if (DEBUG_FOCUS) {
-                ALOGD("Dropping event delivery to target with channel '%s' because it "
-                      "is no longer registered with the input dispatcher.",
-                      inputTarget.inputChannel->getName().c_str());
+            if (DEBUG_DROPPED_EVENTS_VERBOSE) {
+                LOG(INFO) << "Dropping event delivery to target with channel "
+                          << inputTarget.inputChannel->getName()
+                          << " because it is no longer registered with the input dispatcher.";
             }
         }
     }
@@ -2129,9 +2161,8 @@
 }
 
 sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
-        nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+        nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
         InputEventInjectionResult& outInjectionResult) {
-    std::string reason;
     outInjectionResult = InputEventInjectionResult::FAILED; // Default result
 
     int32_t displayId = getTargetDisplayId(entry);
@@ -2169,7 +2200,7 @@
             ALOGW("Waiting because no window has focus but %s may eventually add a "
                   "window when it finishes starting up. Will wait for %" PRId64 "ms",
                   mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
-            *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
+            nextWakeupTime = std::min(nextWakeupTime, *mNoFocusedWindowTimeoutTime);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
@@ -2214,7 +2245,7 @@
     // prior input events.
     if (entry.type == EventEntry::Type::KEY) {
         if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
-            *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
+            nextWakeupTime = std::min(nextWakeupTime, *mKeyIsWaitingForEventsTimeout);
             outInjectionResult = InputEventInjectionResult::PENDING;
             return nullptr;
         }
@@ -2300,6 +2331,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);
@@ -2309,7 +2347,7 @@
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
         const auto [x, y] = resolveTouchedPosition(entry);
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-        const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+        const PointerProperties& pointer = entry.pointerProperties[pointerIndex];
         // Outside targets should be added upon first dispatched DOWN event. That means, this should
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
@@ -2317,7 +2355,7 @@
                 findTouchedWindowAtLocked(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
+            targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id);
         }
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
@@ -2375,11 +2413,11 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
             }
 
             // 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.
@@ -2398,12 +2436,10 @@
             // Update the temporary touch state.
 
             if (!isHoverAction) {
-                std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                pointerIds.set(pointerId);
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-                tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId,
-                                                 pointerIds,
+                tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
+                                                 targetFlags, entry.deviceId, {pointer},
                                                  isDownOrPointerDown
                                                          ? std::make_optional(entry.eventTime)
                                                          : std::nullopt);
@@ -2420,13 +2456,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, {pointer},
+                                                         entry.eventTime);
                     }
                 }
             }
@@ -2436,12 +2473,12 @@
         // make it pilfering. This will prevent other non-spy windows from getting this pointer,
         // which is a specific behaviour that we want.
         for (TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
+            if (touchedWindow.hasTouchingPointer(entry.deviceId, pointer.id) &&
                 touchedWindow.hasPilferingPointers(entry.deviceId)) {
                 // This window is already pilfering some pointers, and this new pointer is also
                 // going to it. Therefore, take over this pointer and don't give it to anyone
                 // else.
-                touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
+                touchedWindow.addPilferingPointer(entry.deviceId, pointer.id);
             }
         }
 
@@ -2453,10 +2490,11 @@
         // If the pointer is not currently down, then ignore the event.
         if (!tempTouchState.isDown(entry.deviceId) &&
             maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-            LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
-                      << " is not down or we previously "
-                         "dropped the pointer down event in display "
-                      << displayId << ": " << entry.getDescription();
+            if (DEBUG_DROPPED_EVENTS_VERBOSE) {
+                LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
+                          << " is not down or we previously dropped the pointer down event in "
+                          << "display " << displayId << ": " << entry.getDescription();
+            }
             outInjectionResult = InputEventInjectionResult::FAILED;
             return {};
         }
@@ -2509,14 +2547,14 @@
 
                 // Make a slippery exit from the old window.
                 std::bitset<MAX_POINTER_ID + 1> pointerIds;
-                const int32_t pointerId = entry.pointerProperties[0].id;
-                pointerIds.set(pointerId);
+                const PointerProperties& pointer = entry.pointerProperties[0];
+                pointerIds.set(pointer.id);
 
                 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);
 
@@ -2525,8 +2563,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;
                 }
@@ -2539,13 +2576,15 @@
                     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, {pointer},
+                                                 entry.eventTime);
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
-                                   tempTouchState, entry.deviceId, pointerId, targets);
-                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId,
+                                   tempTouchState, entry.deviceId, pointer, targets);
+                tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
         }
@@ -2554,14 +2593,12 @@
         if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
             // If no split, we suppose all touched windows should receive pointer down.
             const int32_t pointerIndex = MotionEvent::getActionIndex(action);
-            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
-                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+            std::vector<PointerProperties> touchingPointers{entry.pointerProperties[pointerIndex]};
+            for (TouchedWindow& touchedWindow : tempTouchState.windows) {
                 // Ignore drag window for it should just track one pointer.
                 if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
                     continue;
                 }
-                std::bitset<MAX_POINTER_ID + 1> touchingPointers;
-                touchingPointers.set(entry.pointerProperties[pointerIndex].id);
                 touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers);
             }
         }
@@ -2573,7 +2610,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;
@@ -2610,7 +2648,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) {
@@ -2631,13 +2669,13 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+        std::vector<PointerProperties> touchingPointers =
                 touchedWindow.getTouchingPointers(entry.deviceId);
-        if (touchingPointers.none()) {
+        if (touchingPointers.empty()) {
             continue;
         }
-        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                                     touchingPointers,
+        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                     touchedWindow.targetFlags, getPointerIds(touchingPointers),
                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
@@ -2662,7 +2700,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();
@@ -2672,24 +2710,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) {
@@ -2818,7 +2846,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) {
@@ -2828,6 +2856,7 @@
     InputTarget inputTarget;
     inputTarget.inputChannel = inputChannel;
     inputTarget.windowHandle = windowHandle;
+    inputTarget.dispatchMode = dispatchMode;
     inputTarget.flags = targetFlags;
     inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
     inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
@@ -2842,6 +2871,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 {
@@ -2856,7 +2886,8 @@
 
     if (it == inputTargets.end()) {
         std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+                                        firstDownTimeInTarget);
         if (!target) {
             return;
         }
@@ -2864,15 +2895,20 @@
         it = inputTargets.end() - 1;
     }
 
-    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
-    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
+    if (it->flags != targetFlags) {
+        LOG(ERROR) << "Flags don't match! targetFlags=" << targetFlags.string() << ", it=" << *it;
+    }
+    if (it->globalScaleFactor != windowInfo->globalScaleFactor) {
+        LOG(ERROR) << "Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
+                   << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
+    }
 }
 
 void InputDispatcher::addPointerWindowTargetLocked(
         const sp<android::gui::WindowInfoHandle>& windowHandle,
-        ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds,
-        std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const
-        REQUIRES(mLock) {
+        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;
@@ -2893,7 +2929,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();
     }
@@ -2902,7 +2938,8 @@
 
     if (it == inputTargets.end()) {
         std::optional<InputTarget> target =
-                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+                createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+                                        firstDownTimeInTarget);
         if (!target) {
             return;
         }
@@ -2910,8 +2947,18 @@
         it = inputTargets.end() - 1;
     }
 
-    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
-    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
+    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) << __func__ << ": Flags don't match! new targetFlags=" << targetFlags.string()
+                   << ", it=" << *it;
+    }
+    if (it->globalScaleFactor != windowInfo->globalScaleFactor) {
+        LOG(ERROR) << __func__ << ": Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
+                   << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
+    }
 
     it->addPointers(pointerIds, windowInfo->transform);
 }
@@ -2924,7 +2971,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()) {
@@ -3196,7 +3242,7 @@
 
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                  const std::shared_ptr<Connection>& connection,
-                                                 std::shared_ptr<EventEntry> eventEntry,
+                                                 std::shared_ptr<const EventEntry> eventEntry,
                                                  const InputTarget& inputTarget) {
     ATRACE_NAME_IF(ATRACE_ENABLED(),
                    StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
@@ -3251,41 +3297,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<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()) {
@@ -3294,31 +3328,21 @@
 }
 
 void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
-                                                 std::shared_ptr<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;
-
+                                                 std::shared_ptr<const EventEntry> eventEntry,
+                                                 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.
-    EventEntry& newEntry = *(dispatchEntry->eventEntry);
+    eventEntry = dispatchEntry->eventEntry;
     // Apply target flags and update the connection's input state.
-    switch (newEntry.type) {
+    switch (eventEntry->type) {
         case EventEntry::Type::KEY: {
-            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(newEntry);
-            if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
-                                                 dispatchEntry->resolvedFlags)) {
+            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*eventEntry);
+            if (!connection->inputState.trackKey(keyEntry, keyEntry.flags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
                              << "~ dropping inconsistent event: " << *dispatchEntry;
                 return; // skip the inconsistent event
@@ -3327,94 +3351,128 @@
         }
 
         case EventEntry::Type::MOTION: {
-            const MotionEntry& motionEntry = static_cast<const MotionEntry&>(newEntry);
-            // Assign a default value to dispatchEntry that will never be generated by InputReader,
-            // and assign a InputDispatcher value if it doesn't change in the if-else chain below.
-            constexpr int32_t DEFAULT_RESOLVED_EVENT_ID =
-                    static_cast<int32_t>(IdGenerator::Source::OTHER);
-            dispatchEntry->resolvedEventId = DEFAULT_RESOLVED_EVENT_ID;
-            if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
-            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
-            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_ENTER)) {
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
-            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
-            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
-            } else {
-                dispatchEntry->resolvedEventId = motionEntry.id;
-            }
-            if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
-                !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
-                                                   motionEntry.displayId)) {
-                if (DEBUG_DISPATCH_CYCLE) {
-                    ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover "
-                          "enter event",
-                          connection->getInputChannelName().c_str());
-                }
-                // We keep the 'resolvedEventId' here equal to the original 'motionEntry.id' because
-                // this is a one-to-one event conversion.
-                dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
-            }
+            std::shared_ptr<const MotionEntry> resolvedMotion =
+                    std::static_pointer_cast<const MotionEntry>(eventEntry);
+            {
+                // Determine the resolved motion entry.
+                const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
+                int32_t resolvedAction = motionEntry.action;
+                int32_t resolvedFlags = motionEntry.flags;
 
-            if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
-                dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
-            }
-            if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
-                dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
-            }
-            if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) {
-                dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+                if (inputTarget.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
+                    resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_EXIT) {
+                    resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_ENTER) {
+                    resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_EXIT) {
+                    resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+                } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_ENTER) {
+                    resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+                }
+                if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
+                    !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
+                                                       motionEntry.displayId)) {
+                    if (DEBUG_DISPATCH_CYCLE) {
+                        LOG(DEBUG) << "channel '" << connection->getInputChannelName().c_str()
+                                   << "' ~ enqueueDispatchEntryLocked: filling in missing hover "
+                                      "enter event";
+                    }
+                    resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+                }
+
+                if (resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+                    resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
+                }
+                if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
+                    resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+                }
+                if (dispatchEntry->targetFlags.test(
+                            InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) {
+                    resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+                }
+
+                dispatchEntry->resolvedFlags = resolvedFlags;
+                if (resolvedAction != motionEntry.action) {
+                    std::optional<std::vector<PointerProperties>> usingProperties;
+                    std::optional<std::vector<PointerCoords>> usingCoords;
+                    if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+                        resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+                        // This is a HOVER_EXIT or an ACTION_CANCEL event that was synthesized by
+                        // the dispatcher, and therefore the coordinates of this event are currently
+                        // incorrect. These events should use the coordinates of the last dispatched
+                        // ACTION_MOVE or HOVER_MOVE. We need to query InputState to get this data.
+                        const bool hovering = resolvedAction == AMOTION_EVENT_ACTION_HOVER_EXIT;
+                        std::optional<std::pair<std::vector<PointerProperties>,
+                                                std::vector<PointerCoords>>>
+                                pointerInfo =
+                                        connection->inputState.getPointersOfLastEvent(motionEntry,
+                                                                                      hovering);
+                        if (pointerInfo) {
+                            usingProperties = pointerInfo->first;
+                            usingCoords = pointerInfo->second;
+                        }
+                    }
+                    // Generate a new MotionEntry with a new eventId using the resolved action and
+                    // flags.
+                    resolvedMotion = std::make_shared<
+                            MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
+                                         motionEntry.eventTime, motionEntry.deviceId,
+                                         motionEntry.source, motionEntry.displayId,
+                                         motionEntry.policyFlags, resolvedAction,
+                                         motionEntry.actionButton, resolvedFlags,
+                                         motionEntry.metaState, motionEntry.buttonState,
+                                         motionEntry.classification, motionEntry.edgeFlags,
+                                         motionEntry.xPrecision, motionEntry.yPrecision,
+                                         motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+                                         motionEntry.downTime,
+                                         usingProperties.value_or(motionEntry.pointerProperties),
+                                         usingCoords.value_or(motionEntry.pointerCoords));
+                    if (ATRACE_ENABLED()) {
+                        std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
+                                                           ") to MotionEvent(id=0x%" PRIx32 ").",
+                                                           motionEntry.id, resolvedMotion->id);
+                        ATRACE_NAME(message.c_str());
+                    }
+
+                    // Set the resolved motion entry in the DispatchEntry.
+                    dispatchEntry->eventEntry = resolvedMotion;
+                    eventEntry = resolvedMotion;
+                }
             }
 
             // Check if we need to cancel any of the ongoing gestures. We don't support multiple
             // devices being active at the same time in the same window, so if a new device is
             // active, cancel the gesture from the old device.
-
             std::unique_ptr<EventEntry> cancelEvent =
-                    connection->inputState
-                            .cancelConflictingInputStream(motionEntry,
-                                                          dispatchEntry->resolvedAction);
+                    connection->inputState.cancelConflictingInputStream(*resolvedMotion);
             if (cancelEvent != nullptr) {
-                LOG(INFO) << "Canceling pointers for device " << motionEntry.deviceId << " in "
+                LOG(INFO) << "Canceling pointers for device " << resolvedMotion->deviceId << " in "
                           << connection->getInputChannelName() << " with event "
                           << 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.
                 connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry));
             }
 
-            if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
+            if (!connection->inputState.trackMotion(*resolvedMotion,
                                                     dispatchEntry->resolvedFlags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
                              << "~ dropping inconsistent event: " << *dispatchEntry;
                 return; // skip the inconsistent event
             }
 
-            dispatchEntry->resolvedEventId =
-                    dispatchEntry->resolvedEventId == DEFAULT_RESOLVED_EVENT_ID
-                    ? mIdGenerator.nextId()
-                    : motionEntry.id;
-            if (ATRACE_ENABLED() && dispatchEntry->resolvedEventId != motionEntry.id) {
-                std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
-                                                   ") to MotionEvent(id=0x%" PRIx32 ").",
-                                                   motionEntry.id, dispatchEntry->resolvedEventId);
-                ATRACE_NAME(message.c_str());
-            }
-
-            if ((motionEntry.flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
-                (motionEntry.policyFlags & POLICY_FLAG_TRUSTED)) {
+            if ((resolvedMotion->flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
+                (resolvedMotion->policyFlags & POLICY_FLAG_TRUSTED)) {
                 // Skip reporting pointer down outside focus to the policy.
                 break;
             }
 
-            dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
+            dispatchPointerDownOutsideFocus(resolvedMotion->source, resolvedMotion->action,
                                             inputTarget.inputChannel->getConnectionToken());
 
             break;
@@ -3432,14 +3490,14 @@
         case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET: {
             LOG_ALWAYS_FATAL("%s events should not go to apps",
-                             ftl::enum_string(newEntry.type).c_str());
+                             ftl::enum_string(eventEntry->type).c_str());
             break;
         }
     }
 
     // Remember that we are waiting for this dispatch to complete.
     if (dispatchEntry->hasForegroundTarget()) {
-        incrementPendingForegroundDispatches(newEntry);
+        incrementPendingForegroundDispatches(*eventEntry);
     }
 
     // Enqueue the dispatch entry.
@@ -3494,7 +3552,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
         }
 
@@ -3588,18 +3646,17 @@
 
     // Publish the motion event.
     return connection.inputPublisher
-            .publishMotionEvent(dispatchEntry.seq, dispatchEntry.resolvedEventId,
-                                motionEntry.deviceId, motionEntry.source, motionEntry.displayId,
-                                std::move(hmac), dispatchEntry.resolvedAction,
-                                motionEntry.actionButton, dispatchEntry.resolvedFlags,
-                                motionEntry.edgeFlags, motionEntry.metaState,
-                                motionEntry.buttonState, motionEntry.classification,
-                                dispatchEntry.transform, motionEntry.xPrecision,
-                                motionEntry.yPrecision, motionEntry.xCursorPosition,
-                                motionEntry.yCursorPosition, dispatchEntry.rawTransform,
-                                motionEntry.downTime, motionEntry.eventTime,
-                                motionEntry.getPointerCount(), motionEntry.pointerProperties.data(),
-                                usingCoords);
+            .publishMotionEvent(dispatchEntry.seq, motionEntry.id, motionEntry.deviceId,
+                                motionEntry.source, motionEntry.displayId, std::move(hmac),
+                                motionEntry.action, motionEntry.actionButton,
+                                dispatchEntry.resolvedFlags, motionEntry.edgeFlags,
+                                motionEntry.metaState, motionEntry.buttonState,
+                                motionEntry.classification, dispatchEntry.transform,
+                                motionEntry.xPrecision, motionEntry.yPrecision,
+                                motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+                                dispatchEntry.rawTransform, motionEntry.downTime,
+                                motionEntry.eventTime, motionEntry.getPointerCount(),
+                                motionEntry.pointerProperties.data(), usingCoords);
 }
 
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
@@ -3631,14 +3688,13 @@
 
                 // Publish the key event.
                 status = connection->inputPublisher
-                                 .publishKeyEvent(dispatchEntry->seq,
-                                                  dispatchEntry->resolvedEventId, keyEntry.deviceId,
-                                                  keyEntry.source, keyEntry.displayId,
-                                                  std::move(hmac), dispatchEntry->resolvedAction,
-                                                  dispatchEntry->resolvedFlags, keyEntry.keyCode,
-                                                  keyEntry.scanCode, keyEntry.metaState,
-                                                  keyEntry.repeatCount, keyEntry.downTime,
-                                                  keyEntry.eventTime);
+                                 .publishKeyEvent(dispatchEntry->seq, keyEntry.id,
+                                                  keyEntry.deviceId, keyEntry.source,
+                                                  keyEntry.displayId, std::move(hmac),
+                                                  keyEntry.action, dispatchEntry->resolvedFlags,
+                                                  keyEntry.keyCode, keyEntry.scanCode,
+                                                  keyEntry.metaState, keyEntry.repeatCount,
+                                                  keyEntry.downTime, keyEntry.eventTime);
                 break;
             }
 
@@ -3756,7 +3812,7 @@
 
 const std::array<uint8_t, 32> InputDispatcher::getSignature(
         const MotionEntry& motionEntry, const DispatchEntry& dispatchEntry) const {
-    const int32_t actionMasked = MotionEvent::getActionMasked(dispatchEntry.resolvedAction);
+    const int32_t actionMasked = MotionEvent::getActionMasked(motionEntry.action);
     if (actionMasked != AMOTION_EVENT_ACTION_UP && actionMasked != AMOTION_EVENT_ACTION_DOWN) {
         // Only sign events up and down events as the purely move events
         // are tied to their up/down counterparts so signing would be redundant.
@@ -3774,7 +3830,6 @@
         const KeyEntry& keyEntry, const DispatchEntry& dispatchEntry) const {
     VerifiedKeyEvent verifiedEvent = verifiedKeyEventFromKeyEntry(keyEntry);
     verifiedEvent.flags = dispatchEntry.resolvedFlags & VERIFIED_KEY_EVENT_FLAGS;
-    verifiedEvent.action = dispatchEntry.resolvedAction;
     return sign(verifiedEvent);
 }
 
@@ -3948,16 +4003,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;
     }
@@ -3970,6 +4015,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.",
@@ -3983,8 +4029,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++) {
@@ -3998,8 +4043,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);
                 }
@@ -4018,8 +4063,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);
@@ -4049,8 +4103,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.
@@ -4093,8 +4146,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});
@@ -4123,8 +4177,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.
@@ -4226,7 +4279,8 @@
                                 ").",
                                 originalMotionEntry.id, newId));
     std::unique_ptr<MotionEntry> splitMotionEntry =
-            std::make_unique<MotionEntry>(newId, originalMotionEntry.eventTime,
+            std::make_unique<MotionEntry>(newId, originalMotionEntry.injectionState,
+                                          originalMotionEntry.eventTime,
                                           originalMotionEntry.deviceId, originalMotionEntry.source,
                                           originalMotionEntry.displayId,
                                           originalMotionEntry.policyFlags, action,
@@ -4241,11 +4295,6 @@
                                           originalMotionEntry.yCursorPosition, splitDownTime,
                                           splitPointerProperties, splitPointerCoords);
 
-    if (originalMotionEntry.injectionState) {
-        splitMotionEntry->injectionState = originalMotionEntry.injectionState;
-        splitMotionEntry->injectionState->refCount += 1;
-    }
-
     return splitMotionEntry;
 }
 
@@ -4333,9 +4382,10 @@
         }
 
         std::unique_ptr<KeyEntry> newEntry =
-                std::make_unique<KeyEntry>(args.id, args.eventTime, args.deviceId, args.source,
-                                           args.displayId, policyFlags, args.action, flags, keyCode,
-                                           args.scanCode, metaState, repeatCount, args.downTime);
+                std::make_unique<KeyEntry>(args.id, /*injectionState=*/nullptr, args.eventTime,
+                                           args.deviceId, args.source, args.displayId, policyFlags,
+                                           args.action, flags, keyCode, args.scanCode, metaState,
+                                           repeatCount, args.downTime);
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
@@ -4404,7 +4454,8 @@
     policyFlags |= POLICY_FLAG_TRUSTED;
 
     android::base::Timer t;
-    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
+    mPolicy.interceptMotionBeforeQueueing(args.displayId, args.source, args.action, args.eventTime,
+                                          policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
               std::to_string(t.duration().count()).c_str());
@@ -4419,7 +4470,8 @@
             const auto touchStateIt = mTouchStatesByDisplay.find(args.displayId);
             if (touchStateIt != mTouchStatesByDisplay.end()) {
                 const TouchState& touchState = touchStateIt->second;
-                if (touchState.hasTouchingPointers(args.deviceId)) {
+                if (touchState.hasTouchingPointers(args.deviceId) ||
+                    touchState.hasHoveringPointers(args.deviceId)) {
                     policyFlags |= POLICY_FLAG_PASS_TO_USER;
                 }
             }
@@ -4452,14 +4504,14 @@
 
         // Just enqueue a new motion event.
         std::unique_ptr<MotionEntry> newEntry =
-                std::make_unique<MotionEntry>(args.id, args.eventTime, args.deviceId, args.source,
-                                              args.displayId, policyFlags, args.action,
-                                              args.actionButton, args.flags, args.metaState,
-                                              args.buttonState, args.classification, args.edgeFlags,
-                                              args.xPrecision, args.yPrecision,
-                                              args.xCursorPosition, args.yCursorPosition,
-                                              args.downTime, args.pointerProperties,
-                                              args.pointerCoords);
+                std::make_unique<MotionEntry>(args.id, /*injectionState=*/nullptr, args.eventTime,
+                                              args.deviceId, args.source, args.displayId,
+                                              policyFlags, args.action, args.actionButton,
+                                              args.flags, args.metaState, args.buttonState,
+                                              args.classification, args.edgeFlags, args.xPrecision,
+                                              args.yPrecision, args.xCursorPosition,
+                                              args.yCursorPosition, args.downTime,
+                                              args.pointerProperties, args.pointerCoords);
 
         if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
@@ -4605,6 +4657,9 @@
         resolvedDeviceId = event->getDeviceId();
     }
 
+    const bool isAsync = syncMode == InputEventInjectionSync::NONE;
+    auto injectionState = std::make_shared<InjectionState>(targetUid, isAsync);
+
     std::queue<std::unique_ptr<EventEntry>> injectedEntries;
     switch (event->getType()) {
         case InputEventType::KEY: {
@@ -4637,10 +4692,11 @@
 
             mLock.lock();
             std::unique_ptr<KeyEntry> injectedEntry =
-                    std::make_unique<KeyEntry>(incomingKey.getId(), incomingKey.getEventTime(),
-                                               resolvedDeviceId, incomingKey.getSource(),
-                                               incomingKey.getDisplayId(), policyFlags, action,
-                                               flags, keyCode, incomingKey.getScanCode(), metaState,
+                    std::make_unique<KeyEntry>(incomingKey.getId(), injectionState,
+                                               incomingKey.getEventTime(), resolvedDeviceId,
+                                               incomingKey.getSource(), incomingKey.getDisplayId(),
+                                               policyFlags, action, flags, keyCode,
+                                               incomingKey.getScanCode(), metaState,
                                                incomingKey.getRepeatCount(),
                                                incomingKey.getDownTime());
             injectedEntries.push(std::move(injectedEntry));
@@ -4660,7 +4716,9 @@
             if (!(policyFlags & POLICY_FLAG_FILTERED)) {
                 nsecs_t eventTime = motionEvent.getEventTime();
                 android::base::Timer t;
-                mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
+                mPolicy.interceptMotionBeforeQueueing(displayId, motionEvent.getSource(),
+                                                      motionEvent.getAction(), eventTime,
+                                                      /*byref*/ policyFlags);
                 if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
                     ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
                           std::to_string(t.duration().count()).c_str());
@@ -4680,9 +4738,10 @@
 
             const PointerCoords* samplePointerCoords = motionEvent.getSamplePointerCoords();
             std::unique_ptr<MotionEntry> injectedEntry =
-                    std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
-                                                  resolvedDeviceId, motionEvent.getSource(),
-                                                  displayId, policyFlags, motionEvent.getAction(),
+                    std::make_unique<MotionEntry>(motionEvent.getId(), injectionState,
+                                                  *sampleEventTimes, resolvedDeviceId,
+                                                  motionEvent.getSource(), displayId, policyFlags,
+                                                  motionEvent.getAction(),
                                                   motionEvent.getActionButton(), flags,
                                                   motionEvent.getMetaState(),
                                                   motionEvent.getButtonState(),
@@ -4702,9 +4761,10 @@
                 sampleEventTimes += 1;
                 samplePointerCoords += motionEvent.getPointerCount();
                 std::unique_ptr<MotionEntry> nextInjectedEntry = std::make_unique<
-                        MotionEntry>(motionEvent.getId(), *sampleEventTimes, resolvedDeviceId,
-                                     motionEvent.getSource(), displayId, policyFlags,
-                                     motionEvent.getAction(), motionEvent.getActionButton(), flags,
+                        MotionEntry>(motionEvent.getId(), injectionState, *sampleEventTimes,
+                                     resolvedDeviceId, motionEvent.getSource(), displayId,
+                                     policyFlags, motionEvent.getAction(),
+                                     motionEvent.getActionButton(), flags,
                                      motionEvent.getMetaState(), motionEvent.getButtonState(),
                                      motionEvent.getClassification(), motionEvent.getEdgeFlags(),
                                      motionEvent.getXPrecision(), motionEvent.getYPrecision(),
@@ -4726,14 +4786,6 @@
             return InputEventInjectionResult::FAILED;
     }
 
-    InjectionState* injectionState = new InjectionState(targetUid);
-    if (syncMode == InputEventInjectionSync::NONE) {
-        injectionState->injectionIsAsync = true;
-    }
-
-    injectionState->refCount += 1;
-    injectedEntries.back()->injectionState = injectionState;
-
     bool needWake = false;
     while (!injectedEntries.empty()) {
         if (DEBUG_INJECTION) {
@@ -4796,8 +4848,6 @@
                 }
             }
         }
-
-        injectionState->release();
     } // release lock
 
     if (DEBUG_INJECTION) {
@@ -4841,39 +4891,42 @@
     return result;
 }
 
-void InputDispatcher::setInjectionResult(EventEntry& entry,
+void InputDispatcher::setInjectionResult(const EventEntry& entry,
                                          InputEventInjectionResult injectionResult) {
-    InjectionState* injectionState = entry.injectionState;
-    if (injectionState) {
-        if (DEBUG_INJECTION) {
-            LOG(INFO) << "Setting input event injection result to "
-                      << ftl::enum_string(injectionResult);
-        }
-
-        if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
-            // Log the outcome since the injector did not wait for the injection result.
-            switch (injectionResult) {
-                case InputEventInjectionResult::SUCCEEDED:
-                    ALOGV("Asynchronous input event injection succeeded.");
-                    break;
-                case InputEventInjectionResult::TARGET_MISMATCH:
-                    ALOGV("Asynchronous input event injection target mismatch.");
-                    break;
-                case InputEventInjectionResult::FAILED:
-                    ALOGW("Asynchronous input event injection failed.");
-                    break;
-                case InputEventInjectionResult::TIMED_OUT:
-                    ALOGW("Asynchronous input event injection timed out.");
-                    break;
-                case InputEventInjectionResult::PENDING:
-                    ALOGE("Setting result to 'PENDING' for asynchronous injection");
-                    break;
-            }
-        }
-
-        injectionState->injectionResult = injectionResult;
-        mInjectionResultAvailable.notify_all();
+    if (!entry.injectionState) {
+        // Not an injected event.
+        return;
     }
+
+    InjectionState& injectionState = *entry.injectionState;
+    if (DEBUG_INJECTION) {
+        LOG(INFO) << "Setting input event injection result to "
+                  << ftl::enum_string(injectionResult);
+    }
+
+    if (injectionState.injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
+        // Log the outcome since the injector did not wait for the injection result.
+        switch (injectionResult) {
+            case InputEventInjectionResult::SUCCEEDED:
+                ALOGV("Asynchronous input event injection succeeded.");
+                break;
+            case InputEventInjectionResult::TARGET_MISMATCH:
+                ALOGV("Asynchronous input event injection target mismatch.");
+                break;
+            case InputEventInjectionResult::FAILED:
+                ALOGW("Asynchronous input event injection failed.");
+                break;
+            case InputEventInjectionResult::TIMED_OUT:
+                ALOGW("Asynchronous input event injection timed out.");
+                break;
+            case InputEventInjectionResult::PENDING:
+                ALOGE("Setting result to 'PENDING' for asynchronous injection");
+                break;
+        }
+    }
+
+    injectionState.injectionResult = injectionResult;
+    mInjectionResultAvailable.notify_all();
 }
 
 void InputDispatcher::transformMotionEntryForInjectionLocked(
@@ -4899,19 +4952,17 @@
     }
 }
 
-void InputDispatcher::incrementPendingForegroundDispatches(EventEntry& entry) {
-    InjectionState* injectionState = entry.injectionState;
-    if (injectionState) {
-        injectionState->pendingForegroundDispatches += 1;
+void InputDispatcher::incrementPendingForegroundDispatches(const EventEntry& entry) {
+    if (entry.injectionState) {
+        entry.injectionState->pendingForegroundDispatches += 1;
     }
 }
 
-void InputDispatcher::decrementPendingForegroundDispatches(EventEntry& entry) {
-    InjectionState* injectionState = entry.injectionState;
-    if (injectionState) {
-        injectionState->pendingForegroundDispatches -= 1;
+void InputDispatcher::decrementPendingForegroundDispatches(const EventEntry& entry) {
+    if (entry.injectionState) {
+        entry.injectionState->pendingForegroundDispatches -= 1;
 
-        if (injectionState->pendingForegroundDispatches == 0) {
+        if (entry.injectionState->pendingForegroundDispatches == 0) {
             mInjectionSyncFinished.notify_all();
         }
     }
@@ -5035,6 +5086,13 @@
         return false;
     }
 
+    // Ignore touches if stylus is down anywhere on screen
+    if (info.inputConfig.test(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH) &&
+        isStylusActiveInDisplay(info.displayId, mTouchStatesByDisplay)) {
+        LOG(INFO) << "Dropping touch from " << window->getName() << " because stylus is active";
+        return false;
+    }
+
     return true;
 }
 
@@ -5113,7 +5171,7 @@
         for (const sp<WindowInfoHandle>& iwh : windowInfoHandles) {
             windowList += iwh->getName() + " ";
         }
-        ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());
+        LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList;
     }
 
     // Check preconditions for new input windows
@@ -5469,29 +5527,29 @@
 
         // Erase old window.
         ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->getTouchingPointers(deviceId);
+        std::vector<PointerProperties> pointers = touchedWindow->getTouchingPointers(deviceId);
         sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         state->removeWindowByToken(fromToken);
 
         // 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, pointers, downTimeInTarget);
 
         // Store the dragging window.
         if (isDragDrop) {
-            if (pointerIds.count() != 1) {
+            if (pointers.size() != 1) {
                 ALOGW("The drag and drop cannot be started when there is no pointer or more than 1"
                       " pointer on the window.");
                 return false;
             }
             // Track the pointer id for drag window and generate the drag state.
-            const size_t id = firstMarkedBit(pointerIds);
+            const size_t id = pointers.begin()->id;
             mDragState = std::make_unique<DragState>(toWindowHandle, id);
         }
 
@@ -5508,7 +5566,7 @@
 
             // Check if the wallpaper window should deliver the corresponding event.
             transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
-                                   *state, deviceId, pointerIds);
+                                   *state, deviceId, pointers);
         }
     } // release lock
 
@@ -5671,33 +5729,8 @@
             if (!windowHandles.empty()) {
                 dump += INDENT2 "Windows:\n";
                 for (size_t i = 0; i < windowHandles.size(); i++) {
-                    const sp<WindowInfoHandle>& windowHandle = windowHandles[i];
-                    const WindowInfo* windowInfo = windowHandle->getInfo();
-
-                    dump += StringPrintf(INDENT3 "%zu: name='%s', id=%" PRId32 ", displayId=%d, "
-                                                 "inputConfig=%s, alpha=%.2f, "
-                                                 "frame=[%d,%d][%d,%d], globalScale=%f, "
-                                                 "applicationInfo.name=%s, "
-                                                 "applicationInfo.token=%s, "
-                                                 "touchableRegion=",
-                                         i, windowInfo->name.c_str(), windowInfo->id,
-                                         windowInfo->displayId,
-                                         windowInfo->inputConfig.string().c_str(),
-                                         windowInfo->alpha, windowInfo->frame.left,
-                                         windowInfo->frame.top, windowInfo->frame.right,
-                                         windowInfo->frame.bottom, windowInfo->globalScaleFactor,
-                                         windowInfo->applicationInfo.name.c_str(),
-                                         binderToString(windowInfo->applicationInfo.token).c_str());
-                    dump += dumpRegion(windowInfo->touchableRegion);
-                    dump += StringPrintf(", ownerPid=%s, ownerUid=%s, dispatchingTimeout=%" PRId64
-                                         "ms, hasToken=%s, "
-                                         "touchOcclusionMode=%s\n",
-                                         windowInfo->ownerPid.toString().c_str(),
-                                         windowInfo->ownerUid.toString().c_str(),
-                                         millis(windowInfo->dispatchingTimeout),
-                                         binderToString(windowInfo->token).c_str(),
-                                         toString(windowInfo->touchOcclusionMode).c_str());
-                    windowInfo->transform.dump(dump, "transform", INDENT4);
+                    dump += StringPrintf(INDENT3 "%zu: %s", i,
+                                         streamableToString(*windowHandles[i]).c_str());
                 }
             } else {
                 dump += INDENT2 "Windows: <none>\n";
@@ -5721,7 +5754,7 @@
     // Dump recently dispatched or dropped events from oldest to newest.
     if (!mRecentQueue.empty()) {
         dump += StringPrintf(INDENT "RecentQueue: length=%zu\n", mRecentQueue.size());
-        for (const std::shared_ptr<EventEntry>& entry : mRecentQueue) {
+        for (const std::shared_ptr<const EventEntry>& entry : mRecentQueue) {
             dump += INDENT2;
             dump += entry->getDescription();
             dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5744,7 +5777,7 @@
     // Dump inbound events from oldest to newest.
     if (!mInboundQueue.empty()) {
         dump += StringPrintf(INDENT "InboundQueue: length=%zu\n", mInboundQueue.size());
-        for (const std::shared_ptr<EventEntry>& entry : mInboundQueue) {
+        for (const std::shared_ptr<const EventEntry>& entry : mInboundQueue) {
             dump += INDENT2;
             dump += entry->getDescription();
             dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5786,17 +5819,19 @@
             } else {
                 dump += INDENT3 "WaitQueue: <empty>\n";
             }
-            std::stringstream inputStateDump;
-            inputStateDump << connection->inputState;
-            if (!isEmpty(inputStateDump)) {
+            std::string inputStateDump = streamableToString(connection->inputState);
+            if (!inputStateDump.empty()) {
                 dump += INDENT3 "InputState: ";
-                dump += inputStateDump.str() + "\n";
+                dump += inputStateDump + "\n";
             }
         }
     } else {
         dump += INDENT "Connections: <none>\n";
     }
 
+    dump += "input_flags::remove_app_switch_drops() = ";
+    dump += toString(REMOVE_APP_SWITCH_DROPS);
+    dump += "\n";
     if (isAppSwitchPendingLocked()) {
         dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n",
                              ns2ms(mAppSwitchDueTime - now()));
@@ -5857,7 +5892,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);
@@ -5870,7 +5905,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
 
@@ -5900,7 +5935,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());
@@ -5911,7 +5946,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);
     }
 
@@ -5950,7 +5985,7 @@
         removeMonitorChannelLocked(connectionToken);
     }
 
-    mLooper->removeFd(connection->inputChannel->getFd());
+    mLooper->removeFd(connection->inputChannel->getFd().get());
 
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
@@ -6008,8 +6043,10 @@
                                    "input channel stole pointer stream");
         options.deviceId = deviceId;
         options.displayId = displayId;
-        std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+        std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
+        std::bitset<MAX_POINTER_ID + 1> pointerIds = getPointerIds(pointers);
         options.pointerIds = pointerIds;
+
         std::string canceledWindows;
         for (const TouchedWindow& w : state.windows) {
             const std::shared_ptr<InputChannel> channel =
@@ -6128,7 +6165,7 @@
                                                      uint32_t seq, bool handled,
                                                      nsecs_t consumeTime) {
     // Handle post-event policy actions.
-    bool restartEvent;
+    std::unique_ptr<const KeyEntry> fallbackKeyEntry;
 
     { // Start critical section
         auto dispatchEntryIt =
@@ -6152,15 +6189,9 @@
         }
 
         if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
-            KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry.eventEntry));
-            restartEvent =
+            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(dispatchEntry.eventEntry));
+            fallbackKeyEntry =
                     afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
-        } else if (dispatchEntry.eventEntry->type == EventEntry::Type::MOTION) {
-            MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry.eventEntry));
-            restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry,
-                                                               motionEntry, handled);
-        } else {
-            restartEvent = false;
         }
     } // End critical section: The -LockedInterruptable methods may have released the lock.
 
@@ -6184,12 +6215,12 @@
             }
         }
         traceWaitQueueLength(*connection);
-        if (restartEvent && connection->status == Connection::Status::NORMAL) {
-            connection->outboundQueue.emplace_front(std::move(dispatchEntry));
-            traceOutboundQueueLength(*connection);
-        } else {
-            releaseDispatchEntry(std::move(dispatchEntry));
+        if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
+            const InputTarget target{.inputChannel = connection->inputChannel,
+                                     .flags = dispatchEntry->targetFlags};
+            enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target);
         }
+        releaseDispatchEntry(std::move(dispatchEntry));
     }
 
     // Start the next dispatch cycle for this connection.
@@ -6289,7 +6320,7 @@
 }
 
 void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
-                                                             KeyEntry& entry) {
+                                                             const KeyEntry& entry) {
     const KeyEvent event = createKeyEvent(entry);
     nsecs_t delay = 0;
     { // release lock
@@ -6374,15 +6405,15 @@
     sendWindowResponsiveCommandLocked(connectionToken, pid);
 }
 
-bool InputDispatcher::afterKeyEventLockedInterruptable(
+std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptable(
         const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-        KeyEntry& keyEntry, bool handled) {
+        const KeyEntry& keyEntry, bool handled) {
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
         if (!handled) {
             // Report the key as unhandled, since the fallback was not handled.
             mReporter->reportUnhandledKey(keyEntry.id);
         }
-        return false;
+        return {};
     }
 
     // Get the fallback key state.
@@ -6442,7 +6473,7 @@
                       "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
                       originalKeyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
             }
-            return false;
+            return {};
         }
 
         // Dispatch the unhandled key to the policy.
@@ -6467,7 +6498,7 @@
 
         if (connection->status != Connection::Status::NORMAL) {
             connection->inputState.removeFallbackKey(originalKeyCode);
-            return false;
+            return {};
         }
 
         // Latch the fallback keycode for this key on an initial down.
@@ -6528,25 +6559,22 @@
         }
 
         if (fallback) {
-            // Restart the dispatch cycle using the fallback key.
-            keyEntry.eventTime = event.getEventTime();
-            keyEntry.deviceId = event.getDeviceId();
-            keyEntry.source = event.getSource();
-            keyEntry.displayId = event.getDisplayId();
-            keyEntry.flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
-            keyEntry.keyCode = *fallbackKeyCode;
-            keyEntry.scanCode = event.getScanCode();
-            keyEntry.metaState = event.getMetaState();
-            keyEntry.repeatCount = event.getRepeatCount();
-            keyEntry.downTime = event.getDownTime();
-            keyEntry.syntheticRepeat = false;
-
+            // Return the fallback key that we want dispatched to the channel.
+            std::unique_ptr<KeyEntry> newEntry =
+                    std::make_unique<KeyEntry>(mIdGenerator.nextId(), keyEntry.injectionState,
+                                               event.getEventTime(), event.getDeviceId(),
+                                               event.getSource(), event.getDisplayId(),
+                                               keyEntry.policyFlags, keyEntry.action,
+                                               event.getFlags() | AKEY_EVENT_FLAG_FALLBACK,
+                                               *fallbackKeyCode, event.getScanCode(),
+                                               event.getMetaState(), event.getRepeatCount(),
+                                               event.getDownTime());
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Dispatching fallback key.  "
                       "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
                       originalKeyCode, *fallbackKeyCode, keyEntry.metaState);
             }
-            return true; // restart the event
+            return newEntry;
         } else {
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: No fallback key.");
@@ -6556,13 +6584,7 @@
             mReporter->reportUnhandledKey(keyEntry.id);
         }
     }
-    return false;
-}
-
-bool InputDispatcher::afterMotionEventLockedInterruptable(
-        const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-        MotionEntry& motionEntry, bool handled) {
-    return false;
+    return {};
 }
 
 void InputDispatcher::traceInboundQueueLengthLocked() {
@@ -6820,10 +6842,10 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, int32_t deviceId, int32_t pointerId,
+                                         TouchState& state, int32_t deviceId,
+                                         const PointerProperties& pointerProperties,
                                          std::vector<InputTarget>& targets) const {
-    std::bitset<MAX_POINTER_ID + 1> pointerIds;
-    pointerIds.set(pointerId);
+    std::vector<PointerProperties> pointers{pointerProperties};
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -6839,20 +6861,17 @@
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        addPointerWindowTargetLocked(oldWallpaper,
-                                     oldTouchedWindow.targetFlags |
-                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                                     pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId),
-                                     targets);
-        state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
+        addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
+                                     oldTouchedWindow.targetFlags, getPointerIds(pointers),
+                                     oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
+        state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, 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);
+                                deviceId, pointers);
     }
 }
 
@@ -6861,7 +6880,7 @@
                                              const sp<WindowInfoHandle> fromWindowHandle,
                                              const sp<WindowInfoHandle> toWindowHandle,
                                              TouchState& state, int32_t deviceId,
-                                             std::bitset<MAX_POINTER_ID + 1> pointerIds) {
+                                             const std::vector<PointerProperties>& pointers) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6886,12 +6905,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, pointers, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
                 getConnectionLocked(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
@@ -6925,11 +6943,29 @@
     return nullptr;
 }
 
-void InputDispatcher::setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
+void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+                                                std::chrono::nanoseconds delay) {
     std::scoped_lock _l(mLock);
 
-    mConfig.keyRepeatTimeout = timeout;
-    mConfig.keyRepeatDelay = delay;
+    mConfig.keyRepeatTimeout = timeout.count();
+    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 a1127a0..3567288 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -82,8 +82,6 @@
     static constexpr bool kDefaultInTouchMode = true;
 
     explicit InputDispatcher(InputDispatcherPolicyInterface& policy);
-    explicit InputDispatcher(InputDispatcherPolicyInterface& policy,
-                             std::chrono::nanoseconds staleEventTimeout);
     ~InputDispatcher() override;
 
     void dump(std::string& dump) const override;
@@ -147,7 +145,11 @@
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
-    void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) override;
+    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 {
@@ -172,9 +174,9 @@
 
     sp<Looper> mLooper;
 
-    std::shared_ptr<EventEntry> mPendingEvent GUARDED_BY(mLock);
-    std::deque<std::shared_ptr<EventEntry>> mInboundQueue GUARDED_BY(mLock);
-    std::deque<std::shared_ptr<EventEntry>> mRecentQueue GUARDED_BY(mLock);
+    std::shared_ptr<const EventEntry> mPendingEvent GUARDED_BY(mLock);
+    std::deque<std::shared_ptr<const EventEntry>> mInboundQueue GUARDED_BY(mLock);
+    std::deque<std::shared_ptr<const EventEntry>> mRecentQueue GUARDED_BY(mLock);
 
     // A command entry captures state and behavior for an action to be performed in the
     // dispatch loop after the initial processing has taken place.  It is essentially
@@ -207,7 +209,7 @@
     // This method should only be called on the input dispatcher's own thread.
     void dispatchOnce();
 
-    void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) REQUIRES(mLock);
+    void dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) REQUIRES(mLock);
 
     // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
     bool enqueueInboundEventLocked(std::unique_ptr<EventEntry> entry) REQUIRES(mLock);
@@ -224,7 +226,7 @@
             REQUIRES(mLock);
 
     // Adds an event to a queue of recent events for debugging purposes.
-    void addRecentEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+    void addRecentEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock);
 
     // App switch latency optimization.
     bool mAppSwitchSawKeyDown GUARDED_BY(mLock);
@@ -236,7 +238,7 @@
 
     // Blocked event latency optimization.  Drops old events when the user intends
     // to transfer focus to a new application.
-    std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
+    std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
     sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
             int32_t displayId, float x, float y, bool isStylus = false,
@@ -283,19 +285,19 @@
 
     // Event injection and synchronization.
     std::condition_variable mInjectionResultAvailable;
-    void setInjectionResult(EventEntry& entry,
+    void setInjectionResult(const EventEntry& entry,
                             android::os::InputEventInjectionResult injectionResult);
     void transformMotionEntryForInjectionLocked(MotionEntry&,
                                                 const ui::Transform& injectedTransform) const
             REQUIRES(mLock);
 
     std::condition_variable mInjectionSyncFinished;
-    void incrementPendingForegroundDispatches(EventEntry& entry);
-    void decrementPendingForegroundDispatches(EventEntry& entry);
+    void incrementPendingForegroundDispatches(const EventEntry& entry);
+    void decrementPendingForegroundDispatches(const EventEntry& entry);
 
     // Key repeat tracking.
     struct KeyRepeatState {
-        std::shared_ptr<KeyEntry> lastKeyEntry; // or null if no repeat
+        std::shared_ptr<const KeyEntry> lastKeyEntry; // or null if no repeat
         nsecs_t nextRepeatTime;
     } mKeyRepeatState GUARDED_BY(mLock);
 
@@ -321,7 +323,7 @@
     // Inbound event processing.
     void drainInboundQueueLocked() REQUIRES(mLock);
     void releasePendingEventLocked() REQUIRES(mLock);
-    void releaseInboundEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+    void releaseInboundEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock);
 
     // Dispatch state.
     bool mDispatchEnabled GUARDED_BY(mLock);
@@ -432,23 +434,24 @@
                                             const ConfigurationChangedEntry& entry) REQUIRES(mLock);
     bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry)
             REQUIRES(mLock);
-    bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
-                           DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
-    bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
-    void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry)
+    bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
+                           DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
+    bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry,
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
+    void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry)
             REQUIRES(mLock);
     void dispatchPointerCaptureChangedLocked(
-            nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+            nsecs_t currentTime, const std::shared_ptr<const PointerCaptureChangedEntry>& entry,
             DropReason& dropReason) REQUIRES(mLock);
     void dispatchTouchModeChangeLocked(nsecs_t currentTime,
-                                       const std::shared_ptr<TouchModeEntry>& entry)
+                                       const std::shared_ptr<const TouchModeEntry>& entry)
             REQUIRES(mLock);
-    void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
+    void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<const EventEntry> entry,
                              const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<SensorEntry>& entry,
-                              DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
-    void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<DragEntry> entry) REQUIRES(mLock);
+    void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry,
+                              DropReason* dropReason, nsecs_t& nextWakeupTime) REQUIRES(mLock);
+    void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry)
+            REQUIRES(mLock);
     void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
     void logOutboundMotionDetails(const char* prefix, const MotionEntry& entry);
 
@@ -461,9 +464,6 @@
      */
     std::optional<nsecs_t> mNoFocusedWindowTimeoutTime GUARDED_BY(mLock);
 
-    // Amount of time to allow for an event to be dispatched (measured since its eventTime)
-    // before considering it stale and dropping it.
-    const std::chrono::nanoseconds mStaleEventTimeout;
     bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry);
 
     bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock);
@@ -521,7 +521,7 @@
 
     int32_t getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
-            nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+            nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
             nsecs_t currentTime, const MotionEntry& entry,
@@ -531,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,
@@ -581,15 +583,14 @@
     // If needed, the methods post commands to run later once the critical bits are done.
     void prepareDispatchCycleLocked(nsecs_t currentTime,
                                     const std::shared_ptr<Connection>& connection,
-                                    std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
-            REQUIRES(mLock);
-    void enqueueDispatchEntriesLocked(nsecs_t currentTime,
-                                      const std::shared_ptr<Connection>& connection,
-                                      std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
-            REQUIRES(mLock);
+                                    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<EventEntry>, const InputTarget& inputTarget,
-                                    ftl::Flags<InputTarget::Flags> dispatchMode) REQUIRES(mLock);
+                                    std::shared_ptr<const EventEntry>,
+                                    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);
@@ -650,7 +651,7 @@
                                         const std::shared_ptr<Connection>& connection, uint32_t seq,
                                         bool handled, nsecs_t consumeTime) REQUIRES(mLock);
     void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
-                                                KeyEntry& entry) REQUIRES(mLock);
+                                                const KeyEntry& entry) REQUIRES(mLock);
     void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock);
     void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
             REQUIRES(mLock);
@@ -664,12 +665,10 @@
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
     std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
-    bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
-                                          DispatchEntry& dispatchEntry, KeyEntry& keyEntry,
-                                          bool handled) REQUIRES(mLock);
-    bool afterMotionEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
-                                             DispatchEntry& dispatchEntry, MotionEntry& motionEntry,
-                                             bool handled) REQUIRES(mLock);
+    // Returns a fallback KeyEntry that should be sent to the connection, if required.
+    std::unique_ptr<const KeyEntry> afterKeyEventLockedInterruptable(
+            const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
+            const KeyEntry& keyEntry, bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
     std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
@@ -691,14 +690,15 @@
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t deviceId, int32_t pointerId,
+                            TouchState& state, int32_t deviceId,
+                            const PointerProperties& pointerProperties,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
                                 TouchState& state, int32_t deviceId,
-                                std::bitset<MAX_POINTER_ID + 1> pointerIds) REQUIRES(mLock);
+                                const std::vector<PointerProperties>& pointers) REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 09b5186..1fec9b7 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,19 +24,6 @@
 
 namespace android::inputdispatcher {
 
-namespace {
-bool isHoverAction(int32_t action) {
-    switch (MotionEvent::getActionMasked(action)) {
-        case AMOTION_EVENT_ACTION_HOVER_ENTER:
-        case AMOTION_EVENT_ACTION_HOVER_MOVE:
-        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
-            return true;
-        }
-    }
-    return false;
-}
-} // namespace
-
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
@@ -51,8 +38,8 @@
     return false;
 }
 
-bool InputState::trackKey(const KeyEntry& entry, int32_t action, int32_t flags) {
-    switch (action) {
+bool InputState::trackKey(const KeyEntry& entry, int32_t flags) {
+    switch (entry.action) {
         case AKEY_EVENT_ACTION_UP: {
             if (entry.flags & AKEY_EVENT_FLAG_FALLBACK) {
                 std::erase_if(mFallbackKeys,
@@ -101,7 +88,7 @@
  *  true if the incoming event was correctly tracked,
  *  false if the incoming event should be dropped.
  */
-bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
+bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) {
     // Don't track non-pointer events
     if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
         // This is a focus-dispatched event; we don't track its state.
@@ -113,18 +100,11 @@
         if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
             !isStylusEvent(entry.source, entry.pointerProperties)) {
             // We already have a stylus stream, and the new event is not from stylus.
-            if (!lastMemento.hovering) {
-                // If stylus is currently down, reject the new event unconditionally.
-                return false;
-            }
-        }
-        if (!lastMemento.hovering && isHoverAction(action)) {
-            // Reject hovers if already down
             return false;
         }
     }
 
-    int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+    int32_t actionMasked = entry.action & AMOTION_EVENT_ACTION_MASK;
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_UP:
         case AMOTION_EVENT_ACTION_CANCEL: {
@@ -215,6 +195,16 @@
     }
 }
 
+std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+InputState::getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const {
+    ssize_t index = findMotionMemento(entry, hovering);
+    if (index == -1) {
+        return std::nullopt;
+    }
+    return std::make_pair(mMotionMementos[index].pointerProperties,
+                          mMotionMementos[index].pointerCoords);
+}
+
 ssize_t InputState::findKeyMemento(const KeyEntry& entry) const {
     for (size_t i = 0; i < mKeyMementos.size(); i++) {
         const KeyMemento& memento = mKeyMementos[i];
@@ -301,8 +291,7 @@
     return pointerProperties.size();
 }
 
-bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry,
-                                            int32_t resolvedAction) const {
+bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) const {
     if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) {
         // This is a focus-dispatched event that should not affect the previous stream.
         return false;
@@ -320,7 +309,7 @@
     }
 
     const MotionMemento& lastMemento = mMotionMementos.back();
-    const int32_t actionMasked = MotionEvent::getActionMasked(resolvedAction);
+    const int32_t actionMasked = MotionEvent::getActionMasked(motionEntry.action);
 
     // For compatibility, only one input device can be active at a time in the same window.
     if (lastMemento.deviceId == motionEntry.deviceId) {
@@ -332,16 +321,6 @@
             return true;
         }
 
-        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
-        // If this hover event was generated as a result of the pointer leaving the window,
-        // the HOVER_EXIT event should have the same coordinates as the previous
-        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
-        // coordinates as the previous event by cancelling the stream here. With this approach, the
-        // HOVER_EXIT event is generated from the previous event.
-        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
-            return true;
-        }
-
         // If the stream changes its source, just cancel the current gesture to be safe. It's
         // possible that the app isn't handling source changes properly
         if (motionEntry.source != lastMemento.source) {
@@ -366,19 +345,13 @@
         return false;
     }
 
-    // We want stylus down to block touch and other source types, but stylus hover should not
-    // have such an effect.
-    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
-        // New event is a hover. Keep the current non-hovering gesture instead
-        return false;
-    }
-
-    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
-        // We have non-hovering stylus already active.
+    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+        // A stylus is already active.
         if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
             actionMasked == AMOTION_EVENT_ACTION_DOWN) {
-            // If this new event is a stylus from a different device going down, then cancel the old
-            // stylus and allow the new stylus to take over
+            // If this new event is from a different device, then cancel the old
+            // stylus and allow the new stylus to take over, but only if it's going down.
+            // Otherwise, they will start to race each other.
             return true;
         }
 
@@ -395,9 +368,9 @@
     return false;
 }
 
-std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(const MotionEntry& motionEntry,
-                                                                     int32_t resolvedAction) {
-    if (!shouldCancelPreviousStream(motionEntry, resolvedAction)) {
+std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(
+        const MotionEntry& motionEntry) {
+    if (!shouldCancelPreviousStream(motionEntry)) {
         return {};
     }
 
@@ -407,7 +380,7 @@
     std::unique_ptr<MotionEntry> cancelEntry =
             createCancelEntryForMemento(memento, motionEntry.eventTime);
 
-    if (!trackMotion(*cancelEntry, cancelEntry->action, cancelEntry->flags)) {
+    if (!trackMotion(*cancelEntry, cancelEntry->flags)) {
         LOG(FATAL) << "Generated inconsistent cancel event!";
     }
     return cancelEntry;
@@ -421,9 +394,10 @@
     if (action == AMOTION_EVENT_ACTION_CANCEL) {
         flags |= AMOTION_EVENT_FLAG_CANCELED;
     }
-    return std::make_unique<MotionEntry>(mIdGenerator.nextId(), eventTime, memento.deviceId,
-                                         memento.source, memento.displayId, memento.policyFlags,
-                                         action, /*actionButton=*/0, flags, AMETA_NONE,
+    return std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                         eventTime, memento.deviceId, memento.source,
+                                         memento.displayId, memento.policyFlags, action,
+                                         /*actionButton=*/0, flags, AMETA_NONE,
                                          /*buttonState=*/0, MotionClassification::NONE,
                                          AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                          memento.yPrecision, memento.xCursorPosition,
@@ -437,9 +411,10 @@
     for (KeyMemento& memento : mKeyMementos) {
         if (shouldCancelKey(memento, options)) {
             events.push_back(
-                    std::make_unique<KeyEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
-                                               memento.source, memento.displayId,
-                                               memento.policyFlags, AKEY_EVENT_ACTION_UP,
+                    std::make_unique<KeyEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                               currentTime, memento.deviceId, memento.source,
+                                               memento.displayId, memento.policyFlags,
+                                               AKEY_EVENT_ACTION_UP,
                                                memento.flags | AKEY_EVENT_FLAG_CANCELED,
                                                memento.keyCode, memento.scanCode, memento.metaState,
                                                /*repeatCount=*/0, memento.downTime));
@@ -498,8 +473,8 @@
                             | (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
             events.push_back(
-                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
-                                                  memento.deviceId, memento.source,
+                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                                  currentTime, memento.deviceId, memento.source,
                                                   memento.displayId, memento.policyFlags, action,
                                                   /*actionButton=*/0, memento.flags, AMETA_NONE,
                                                   /*buttonState=*/0, MotionClassification::NONE,
@@ -539,11 +514,11 @@
             flags |= AMOTION_EVENT_FLAG_CANCELED;
         }
         events.push_back(
-                std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
-                                              memento.source, memento.displayId,
-                                              memento.policyFlags, action, /*actionButton=*/0,
-                                              flags, AMETA_NONE, /*buttonState=*/0,
-                                              MotionClassification::NONE,
+                std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                              currentTime, memento.deviceId, memento.source,
+                                              memento.displayId, memento.policyFlags, action,
+                                              /*actionButton=*/0, flags, AMETA_NONE,
+                                              /*buttonState=*/0, MotionClassification::NONE,
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
                                               memento.yCursorPosition, memento.downTime,
@@ -564,8 +539,8 @@
                                                      : AMOTION_EVENT_ACTION_POINTER_UP |
                             (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
             events.push_back(
-                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
-                                                  memento.deviceId, memento.source,
+                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+                                                  currentTime, memento.deviceId, memento.source,
                                                   memento.displayId, memento.policyFlags, action,
                                                   /*actionButton=*/0,
                                                   memento.flags | AMOTION_EVENT_FLAG_CANCELED,
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 686c432..d49469d 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -41,16 +41,24 @@
     // Records tracking information for a key event that has just been published.
     // Returns true if the event should be delivered, false if it is inconsistent
     // and should be skipped.
-    bool trackKey(const KeyEntry& entry, int32_t action, int32_t flags);
+    bool trackKey(const KeyEntry& entry, int32_t flags);
 
     // Records tracking information for a motion event that has just been published.
     // Returns true if the event should be delivered, false if it is inconsistent
     // and should be skipped.
-    bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags);
+    bool trackMotion(const MotionEntry& entry, int32_t flags);
+
+    /**
+     * Return the PointerProperties and the PointerCoords for the last event, if found. Return
+     * std::nullopt if not found. We should not return std::vector<PointerCoords> in isolation,
+     * because the pointers can technically be stored in the vector in any order, so the
+     * PointerProperties are needed to specify the order in which the pointer coords are stored.
+     */
+    std::optional<std::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
+    getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const;
 
     // Create cancel events for the previous stream if the current motionEntry requires it.
-    std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry,
-                                                             int32_t resolvedAction);
+    std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry);
 
     // Synthesizes cancelation events for the current state and resets the tracked state.
     std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents(
@@ -127,7 +135,7 @@
 
     static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
     static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
-    bool shouldCancelPreviousStream(const MotionEntry& motionEntry, int32_t resolvedAction) const;
+    bool shouldCancelPreviousStream(const MotionEntry& motionEntry) const;
     std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento,
                                                              nsecs_t eventTime) const;
 
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..f8aa625 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -71,10 +71,11 @@
 }
 
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
+                                   InputTarget::DispatchMode dispatchMode,
                                    ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                                   std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
+                                   const std::vector<PointerProperties>& touchingPointers,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
-    if (touchingPointerIds.none()) {
+    if (touchingPointers.empty()) {
         LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
         return;
     }
@@ -85,14 +86,12 @@
         // 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.
-            touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+            touchedWindow.addTouchingPointers(deviceId, touchingPointers);
             if (firstDownTimeInTarget) {
                 touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
@@ -101,8 +100,9 @@
     }
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
+    touchedWindow.dispatchMode = dispatchMode;
     touchedWindow.targetFlags = targetFlags;
-    touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
     if (firstDownTimeInTarget) {
         touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
@@ -110,17 +110,17 @@
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, int32_t hoveringPointerId) {
+                                            DeviceId deviceId, const PointerProperties& pointer) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+            touchedWindow.addHoveringPointer(deviceId, pointer);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+    touchedWindow.addHoveringPointer(deviceId, pointer);
     windows.push_back(touchedWindow);
 }
 
@@ -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) {
@@ -248,6 +234,11 @@
     });
 }
 
+bool TouchState::hasActiveStylus() const {
+    return std::any_of(windows.begin(), windows.end(),
+                       [](const TouchedWindow& window) { return window.hasActiveStylus(); });
+}
+
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
                                                                          int32_t pointerId) const {
     std::set<sp<WindowInfoHandle>> out;
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index e79c73b..3d534bc 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -44,17 +44,17 @@
     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,
+                           const std::vector<PointerProperties>& touchingPointers,
                            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, int32_t hoveringPointerId);
-    void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
+                                    DeviceId deviceId, const PointerProperties& pointer);
+    void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
     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,
@@ -73,6 +73,8 @@
     bool isDown(DeviceId deviceId) const;
     bool hasHoveringPointers(DeviceId deviceId) const;
 
+    bool hasActiveStylus() const;
+
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             DeviceId deviceId, int32_t pointerId) const;
     std::string dump() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 5367751..037d7c8 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -26,9 +26,20 @@
 
 namespace inputdispatcher {
 
+namespace {
+
+bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const PointerProperties& properties) {
+                            return properties.id == pointerId;
+                        }) != pointers.end();
+}
+
+} // namespace
+
 bool TouchedWindow::hasHoveringPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.hoveringPointerIds.any()) {
+        if (!state.hoveringPointers.empty()) {
             return true;
         }
     }
@@ -42,7 +53,7 @@
     }
     const DeviceState& state = stateIt->second;
 
-    return state.hoveringPointerIds.any();
+    return !state.hoveringPointers.empty();
 }
 
 void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
@@ -51,7 +62,7 @@
         return;
     }
     DeviceState& state = stateIt->second;
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
     }
@@ -63,22 +74,40 @@
         return false;
     }
     const DeviceState& state = stateIt->second;
-
-    return state.hoveringPointerIds.test(pointerId);
+    return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) {
-    mDeviceStates[deviceId].hoveringPointerIds.set(pointerId);
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
+    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+    const size_t initialSize = hoveringPointers.size();
+    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
+        return properties.id == pointer.id;
+    });
+    if (hoveringPointers.size() != initialSize) {
+        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+    }
+    hoveringPointers.push_back(pointer);
 }
 
 void TouchedWindow::addTouchingPointers(DeviceId deviceId,
-                                        std::bitset<MAX_POINTER_ID + 1> pointers) {
-    mDeviceStates[deviceId].touchingPointerIds |= pointers;
+                                        const std::vector<PointerProperties>& pointers) {
+    std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers;
+    const size_t initialSize = touchingPointers.size();
+    for (const PointerProperties& pointer : pointers) {
+        std::erase_if(touchingPointers, [&pointer](const PointerProperties& properties) {
+            return properties.id == pointer.id;
+        });
+    }
+    if (touchingPointers.size() != initialSize) {
+        LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device "
+                   << deviceId << " already in " << *this;
+    }
+    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
 }
 
 bool TouchedWindow::hasTouchingPointers() const {
     for (const auto& [_, state] : mDeviceStates) {
-        if (state.touchingPointerIds.any()) {
+        if (!state.touchingPointers.empty()) {
             return true;
         }
     }
@@ -86,21 +115,25 @@
 }
 
 bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const {
-    return getTouchingPointers(deviceId).any();
+    return !getTouchingPointers(deviceId).empty();
 }
 
 bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const {
-    return getTouchingPointers(deviceId).test(pointerId);
+    const auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return false;
+    }
+    const DeviceState& state = stateIt->second;
+    return hasPointerId(state.touchingPointers, pointerId);
 }
 
-std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
+std::vector<PointerProperties> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
         return {};
     }
     const DeviceState& state = stateIt->second;
-
-    return state.touchingPointerIds;
+    return state.touchingPointers;
 }
 
 void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
@@ -118,7 +151,10 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds &= ~pointers;
+    std::erase_if(state.touchingPointers, [&pointers](const PointerProperties& properties) {
+        return pointers.test(properties.id);
+    });
+
     state.pilferingPointerIds &= ~pointers;
 
     if (!state.hasPointers()) {
@@ -126,20 +162,30 @@
     }
 }
 
-std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
-    std::set<DeviceId> deviceIds;
-    for (const auto& [deviceId, _] : mDeviceStates) {
-        deviceIds.insert(deviceId);
+bool TouchedWindow::hasActiveStylus() const {
+    for (const auto& [_, state] : mDeviceStates) {
+        for (const PointerProperties& properties : state.touchingPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
+        for (const PointerProperties& properties : state.hoveringPointers) {
+            if (properties.toolType == ToolType::STYLUS) {
+                return true;
+            }
+        }
     }
-    return deviceIds;
+    return false;
 }
 
-std::set<DeviceId> TouchedWindow::getActiveDeviceIds() const {
-    std::set<DeviceId> out;
-    for (const auto& [deviceId, _] : mDeviceStates) {
-        out.emplace(deviceId);
+std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
+    std::set<DeviceId> deviceIds;
+    for (const auto& [deviceId, deviceState] : mDeviceStates) {
+        if (!deviceState.touchingPointers.empty()) {
+            deviceIds.insert(deviceId);
+        }
     }
-    return out;
+    return deviceIds;
 }
 
 bool TouchedWindow::hasPilferingPointers(DeviceId deviceId) const {
@@ -204,7 +250,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.touchingPointerIds.reset();
+    state.touchingPointers.clear();
     state.pilferingPointerIds.reset();
     state.downTimeInTarget.reset();
 
@@ -220,7 +266,9 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.set(pointerId, false);
+    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
+        return properties.id == pointerId;
+    });
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -234,7 +282,7 @@
     }
     DeviceState& state = stateIt->second;
 
-    state.hoveringPointerIds.reset();
+    state.hoveringPointers.clear();
 
     if (!state.hasPointers()) {
         mDeviceStates.erase(stateIt);
@@ -242,11 +290,11 @@
 }
 
 std::string TouchedWindow::deviceStateToString(const TouchedWindow::DeviceState& state) {
-    return StringPrintf("[touchingPointerIds=%s, "
-                        "downTimeInTarget=%s, hoveringPointerIds=%s, pilferingPointerIds=%s]",
-                        bitsetToString(state.touchingPointerIds).c_str(),
+    return StringPrintf("[touchingPointers=%s, "
+                        "downTimeInTarget=%s, hoveringPointers=%s, pilferingPointerIds=%s]",
+                        dumpVector(state.touchingPointers, streamableToString).c_str(),
                         toString(state.downTimeInTarget).c_str(),
-                        bitsetToString(state.hoveringPointerIds).c_str(),
+                        dumpVector(state.hoveringPointers, streamableToString).c_str(),
                         bitsetToString(state.pilferingPointerIds).c_str());
 }
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 6d2283e..0d1531f 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -31,32 +31,26 @@
 // Focus tracking for touch.
 struct TouchedWindow {
     sp<gui::WindowInfoHandle> windowHandle;
+    InputTarget::DispatchMode dispatchMode = InputTarget::DispatchMode::AS_IS;
     ftl::Flags<InputTarget::Flags> targetFlags;
 
     // Hovering
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, int32_t pointerId);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
     bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const;
     bool hasTouchingPointers() const;
     bool hasTouchingPointers(DeviceId deviceId) const;
-    std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const;
-    void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+    std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const;
+    void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& 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.
-     */
+    bool hasActiveStylus() const;
     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;
@@ -76,16 +70,16 @@
 
 private:
     struct DeviceState {
-        std::bitset<MAX_POINTER_ID + 1> touchingPointerIds;
+        std::vector<PointerProperties> touchingPointers;
         // The pointer ids of the pointers that this window is currently pilfering, by device
         std::bitset<MAX_POINTER_ID + 1> pilferingPointerIds;
         // Time at which the first action down occurred on this window, for each device
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::bitset<MAX_POINTER_ID + 1> hoveringPointerIds;
+        std::vector<PointerProperties> hoveringPointers;
 
-        bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); };
+        bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
 
     std::map<DeviceId, DeviceState> mDeviceStates;
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index d099b44..001dc6c 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -221,7 +221,14 @@
     /*
      * Updates key repeat configuration timeout and delay.
      */
-    virtual void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) = 0;
+    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/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index af28e48..9e6209b 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -18,9 +18,12 @@
 
 #include "InputDispatcherConfiguration.h"
 
+#include <android-base/properties.h>
 #include <binder/IBinder.h>
 #include <gui/InputApplication.h>
+#include <gui/PidUid.h>
 #include <input/Input.h>
+#include <input/InputDevice.h>
 #include <utils/RefBase.h>
 #include <set>
 
@@ -96,8 +99,8 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
-                                               uint32_t& policyFlags) = 0;
+    virtual void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+                                               nsecs_t when, uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
     virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
@@ -118,6 +121,16 @@
     /* Poke user activity for an event dispatched to a window. */
     virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
 
+    /*
+     * Return true if the provided event is stale, and false otherwise. Used for determining
+     * whether the dispatcher should drop the event.
+     */
+    virtual bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) {
+        static const std::chrono::duration STALE_EVENT_TIMEOUT =
+                std::chrono::seconds(10) * android::base::HwTimeoutMultiplier();
+        return std::chrono::nanoseconds(currentTime - eventTime) >= STALE_EVENT_TIMEOUT;
+    }
+
     /* Notifies the policy that a pointer down event has occurred outside the current focused
      * window.
      *
@@ -135,7 +148,7 @@
     virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0;
 
     /* Notifies the policy that there was an input device interaction with apps. */
-    virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+    virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                          const std::set<gui::Uid>& uids) = 0;
 };
 
diff --git a/services/inputflinger/include/InputFilterPolicyInterface.h b/services/inputflinger/include/InputFilterPolicyInterface.h
new file mode 100644
index 0000000..4d39b97
--- /dev/null
+++ b/services/inputflinger/include/InputFilterPolicyInterface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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
+
+namespace android {
+
+/**
+ * The InputFilter policy interface.
+ *
+ * This is the interface that InputFilter uses to talk to Input Manager and other system components.
+ */
+class InputFilterPolicyInterface {
+public:
+    virtual ~InputFilterPolicyInterface() = default;
+
+    /**
+     * A callback to notify about sticky modifier state changes when Sticky keys feature is enabled.
+     *
+     * modifierState: Current sticky modifier state which will be sent with all subsequent
+     * KeyEvents. This only includes modifiers that can be 'Sticky' which includes: Meta, Ctrl,
+     * Shift, Alt and AltGr.
+     *
+     * lockedModifierState: Current locked modifier state representing modifiers that don't get
+     * cleared after non-modifier key press. This only includes modifiers that can be 'Sticky' which
+     * includes: Meta, Ctrl, Shift, Alt and AltGr.
+     *
+     * For more information {@see sticky_keys_filter.rs}
+     */
+    virtual void notifyStickyModifierStateChanged(uint32_t modifierState,
+                                                  uint32_t lockedModifierState) = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 25e1d21..efc8b26 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -451,6 +451,15 @@
 
     /* Returns true if any InputConnection is currently active. */
     virtual bool isInputMethodConnectionActive() = 0;
+
+    /* Gets the viewport of a particular display that the pointer device is associated with. If
+     * the pointer device is not associated with any display, it should ADISPLAY_IS_NONE to get
+     * the viewport that should be used. The device should get a new viewport using this method
+     * every time there is a display configuration change. The logical bounds of the viewport should
+     * be used as the range of possible values for pointing devices, like mice and touchpads.
+     */
+    virtual std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 9e020c7..8b47b55 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -38,7 +38,16 @@
      * library, libinputservice, that has the additional dependencies. The PointerController
      * will be mocked when testing PointerChoreographer.
      */
-    virtual std::shared_ptr<PointerControllerInterface> createPointerController() = 0;
+    virtual std::shared_ptr<PointerControllerInterface> createPointerController(
+            PointerControllerInterface::ControllerType type) = 0;
+
+    /**
+     * Notifies the policy that the default pointer displayId has changed. PointerChoreographer is
+     * the single source of truth for all pointers on screen.
+     * @param displayId The updated display on which the mouse cursor is shown
+     * @param position The new position of the mouse cursor on the logical display
+     */
+    virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 95f819a..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;
@@ -48,10 +50,30 @@
  */
 class PointerControllerInterface {
 protected:
-    PointerControllerInterface() { }
-    virtual ~PointerControllerInterface() { }
+    PointerControllerInterface() {}
+    virtual ~PointerControllerInterface() {}
 
 public:
+    /**
+     * Enum used to differentiate various types of PointerControllers for the transition to
+     * using PointerChoreographer.
+     *
+     * TODO(b/293587049): Refactor the PointerController class into different controller types.
+     */
+    enum class ControllerType {
+        // The PointerController that is responsible for drawing all icons.
+        LEGACY,
+        // Represents a single mouse pointer.
+        MOUSE,
+        // Represents multiple touch spots.
+        TOUCH,
+        // Represents a single stylus pointer.
+        STYLUS,
+    };
+
+    /* Dumps the state of the pointer controller. */
+    virtual std::string dump() = 0;
+
     /* Gets the bounds of the region that the pointer can traverse.
      * Returns true if the bounds are available. */
     virtual std::optional<FloatRect> getBounds() const = 0;
@@ -105,7 +127,7 @@
      * pressed (not hovering).
      */
     virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-            BitSet32 spotIdBits, int32_t displayId) = 0;
+                          BitSet32 spotIdBits, int32_t displayId) = 0;
 
     /* Removes all spots. */
     virtual void clearSpots() = 0;
@@ -115,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/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 5766b14..0582649 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -1046,6 +1046,14 @@
     return mReader->mPreventingTouchpadTaps;
 }
 
+void InputReader::ContextImpl::setLastKeyDownTimestamp(nsecs_t when) {
+    mReader->mLastKeyDownTimestamp = when;
+}
+
+nsecs_t InputReader::ContextImpl::getLastKeyDownTimestamp() {
+    return mReader->mLastKeyDownTimestamp;
+}
+
 void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
     // lock is already held by the input loop
     mReader->disableVirtualKeysUntilLocked(time);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 9a297c9..4c78db3 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -158,6 +158,9 @@
         void setPreventingTouchpadTaps(bool prevent) REQUIRES(mReader->mLock)
                 REQUIRES(mLock) override;
         bool isPreventingTouchpadTaps() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
+        void setLastKeyDownTimestamp(nsecs_t when) REQUIRES(mReader->mLock)
+                REQUIRES(mLock) override;
+        nsecs_t getLastKeyDownTimestamp() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
     } mContext;
 
     friend class ContextImpl;
@@ -198,6 +201,9 @@
     // true if tap-to-click on touchpad currently disabled
     bool mPreventingTouchpadTaps GUARDED_BY(mLock){false};
 
+    // records timestamp of the last key press on the physical keyboard
+    nsecs_t mLastKeyDownTimestamp GUARDED_BY(mLock){0};
+
     // low-level input event decoding and device management
     [[nodiscard]] std::list<NotifyArgs> processEventsLocked(const RawEvent* rawEvents, size_t count)
             REQUIRES(mLock);
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index aed7563..69b2315 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -65,6 +65,9 @@
 
     virtual void setPreventingTouchpadTaps(bool prevent) = 0;
     virtual bool isPreventingTouchpadTaps() = 0;
+
+    virtual void setLastKeyDownTimestamp(nsecs_t when) = 0;
+    virtual nsecs_t getLastKeyDownTimestamp() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 79f07a5..58e35a6 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,6 +20,7 @@
 
 #include "CursorInputMapper.h"
 
+#include <com_android_input_flags.h>
 #include <optional>
 
 #include "CursorButtonAccumulator.h"
@@ -29,6 +30,8 @@
 
 #include "input/PrintTools.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 // The default velocity control parameters that has no effect.
@@ -71,7 +74,8 @@
 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
                                      const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
-        mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
+        mLastEventTime(std::numeric_limits<nsecs_t>::min()),
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -87,11 +91,11 @@
     InputMapper::populateDeviceInfo(info);
 
     if (mParameters.mode == Parameters::Mode::POINTER) {
-        if (const auto bounds = mPointerController->getBounds(); bounds) {
-            info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f,
-                                0.0f, 0.0f);
-            info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f,
-                                0.0f, 0.0f);
+        if (!mBoundsInLogicalDisplay.isEmpty()) {
+            info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, mBoundsInLogicalDisplay.left,
+                                mBoundsInLogicalDisplay.right, 0.0f, 0.0f, 0.0f);
+            info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, mBoundsInLogicalDisplay.top,
+                                mBoundsInLogicalDisplay.bottom, 0.0f, 0.0f, 0.0f);
         }
     } else {
         info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
@@ -283,19 +287,22 @@
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mSource == AINPUT_SOURCE_MOUSE) {
-        if (moved || scrolled || buttonsChanged) {
-            mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+        if (!mEnablePointerChoreographer) {
+            if (moved || scrolled || buttonsChanged) {
+                mPointerController->setPresentation(
+                        PointerControllerInterface::Presentation::POINTER);
 
-            if (moved) {
-                mPointerController->move(deltaX, deltaY);
+                if (moved) {
+                    mPointerController->move(deltaX, deltaY);
+                }
+                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
             }
-            mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+            std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
+
+            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         }
-
-        std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
-
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
     } else {
@@ -499,31 +506,53 @@
     const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
 
     mDisplayId = ADISPLAY_ID_NONE;
-    if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+    std::optional<DisplayViewport> resolvedViewport;
+    bool isBoundsSet = false;
+    if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
         // This InputDevice is associated with a viewport.
         // Only generate events for the associated display.
-        const bool mismatchedPointerDisplay =
-                isPointer && (viewport->displayId != mPointerController->getDisplayId());
-        mDisplayId =
-                mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId);
+        mDisplayId = assocViewport->displayId;
+        resolvedViewport = *assocViewport;
+        if (!mEnablePointerChoreographer) {
+            const bool mismatchedPointerDisplay =
+                    isPointer && (assocViewport->displayId != mPointerController->getDisplayId());
+            if (mismatchedPointerDisplay) {
+                // This device's associated display doesn't match PointerController's current
+                // display. Do not associate it with any display.
+                mDisplayId.reset();
+            }
+        }
     } else if (isPointer) {
         // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-        mDisplayId = mPointerController->getDisplayId();
+        if (mEnablePointerChoreographer) {
+            // Always use DISPLAY_ID_NONE for mouse events.
+            // PointerChoreographer will make it target the correct the displayId later.
+            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+            mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+        } else {
+            mDisplayId = mPointerController->getDisplayId();
+            if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+                resolvedViewport = *v;
+            }
+            if (auto bounds = mPointerController->getBounds(); bounds) {
+                mBoundsInLogicalDisplay = *bounds;
+                isBoundsSet = true;
+            }
+        }
     }
 
-    mOrientation = ui::ROTATION_0;
-    const bool isOrientedDevice =
-            (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
-    // InputReader works in the un-rotated display coordinate space, so we don't need to do
-    // anything if the device is already orientation-aware. If the device is not
-    // orientation-aware, then we need to apply the inverse rotation of the display so that
-    // when the display rotation is applied later as a part of the per-window transform, we
-    // get the expected screen coordinates. When pointer capture is enabled, we do not apply any
-    // rotations and report values directly from the input device.
-    if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
-        if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
-            mOrientation = getInverseRotation(viewport->orientation);
-        }
+    mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) ||
+                    mParameters.mode == Parameters::Mode::POINTER_RELATIVE || !resolvedViewport
+            ? ui::ROTATION_0
+            : getInverseRotation(resolvedViewport->orientation);
+
+    if (!isBoundsSet) {
+        mBoundsInLogicalDisplay = resolvedViewport
+                ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+                            static_cast<float>(resolvedViewport->logicalTop),
+                            static_cast<float>(resolvedViewport->logicalRight - 1),
+                            static_cast<float>(resolvedViewport->logicalBottom - 1)}
+                : FloatRect{0, 0, 0, 0};
     }
 
     bumpGeneration();
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index b879bfd..308adaa 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -119,7 +119,8 @@
     // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
     std::optional<int32_t> mDisplayId;
-    ui::Rotation mOrientation;
+    ui::Rotation mOrientation{ui::ROTATION_0};
+    FloatRect mBoundsInLogicalDisplay{};
 
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
@@ -127,6 +128,8 @@
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
+    const bool mEnablePointerChoreographer;
+
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
                                const InputReaderConfiguration& readerConfig);
     void dumpParameters(std::string& dump);
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 531fc67..f068cc8 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -270,7 +270,7 @@
             keyDown.flags = flags;
             mKeyDowns.push_back(keyDown);
         }
-        onKeyDownProcessed();
+        onKeyDownProcessed(downTime);
     } else {
         // Remove key down.
         if (keyDownIndex) {
@@ -448,8 +448,9 @@
     return out;
 }
 
-void KeyboardInputMapper::onKeyDownProcessed() {
+void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
+    context.setLastKeyDownTimestamp(downTime);
     if (context.isPreventingTouchpadTaps()) {
         // avoid pinging java service unnecessarily, just fade pointer again if it became visible
         context.fadePointer();
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 09808df..500256b 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -107,7 +107,7 @@
     void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
     std::optional<DisplayViewport> findViewport(const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
-    void onKeyDownProcessed();
+    void onKeyDownProcessed(nsecs_t downTime);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 9c87c62..2dd05f5 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -131,7 +131,7 @@
                 bumpGeneration();
             }
         }
-        if (shouldSimulateStylusWithTouch() && outPointer.toolType == ToolType::FINGER) {
+        if (mShouldSimulateStylusWithTouch && outPointer.toolType == ToolType::FINGER) {
             outPointer.toolType = ToolType::STYLUS;
         }
 
@@ -177,6 +177,18 @@
     mMultiTouchMotionAccumulator.finishSync();
 }
 
+std::list<NotifyArgs> MultiTouchInputMapper::reconfigure(nsecs_t when,
+                                                         const InputReaderConfiguration& config,
+                                                         ConfigurationChanges changes) {
+    const bool simulateStylusWithTouch =
+            sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
+    if (simulateStylusWithTouch != mShouldSimulateStylusWithTouch) {
+        mShouldSimulateStylusWithTouch = simulateStylusWithTouch;
+        bumpGeneration();
+    }
+    return TouchInputMapper::reconfigure(when, config, changes);
+}
+
 void MultiTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
@@ -211,14 +223,7 @@
 
 bool MultiTouchInputMapper::hasStylus() const {
     return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
-            shouldSimulateStylusWithTouch();
-}
-
-bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
-    static const bool SIMULATE_STYLUS_WITH_TOUCH =
-            sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
-    return SIMULATE_STYLUS_WITH_TOUCH &&
-            mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
+            mShouldSimulateStylusWithTouch;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 1d788df..5c173f3 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -32,6 +32,9 @@
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
 
 protected:
     void syncTouch(nsecs_t when, RawState* outState) override;
@@ -41,13 +44,6 @@
 private:
     explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
                                    const InputReaderConfiguration& readerConfig);
-    // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
-    // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
-    // It is used to simulate stylus events for debugging and testing on a device that does not
-    // support styluses. It can be enabled using
-    // "adb shell setprop persist.debug.input.simulate_stylus_with_touch true",
-    // and requires a reboot to take effect.
-    inline bool shouldSimulateStylusWithTouch() const;
 
     // If the slot is in use, return the bit id. Return std::nullopt otherwise.
     std::optional<int32_t> getActiveBitId(const MultiTouchMotionAccumulator::Slot& inSlot);
@@ -58,6 +54,15 @@
     int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
 
     bool mStylusMtToolSeen{false};
+
+    // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
+    // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
+    // It is used to simulate stylus events for debugging and testing on a device that does not
+    // support styluses. It can be enabled using
+    // "adb shell setprop debug.input.simulate_stylus_with_touch true".
+    // After enabling, the touchscreen will need to be reconfigured. A reconfiguration usually
+    // happens when turning the screen on/off or by rotating the device orientation.
+    bool mShouldSimulateStylusWithTouch{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index c76fec7..255f02d 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -21,9 +21,11 @@
 #include <iterator>
 #include <limits>
 #include <map>
+#include <mutex>
 #include <optional>
 
 #include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
 #include <android/input.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
@@ -156,13 +158,20 @@
         return sAccumulator;
     }
 
-    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; }
+    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].fingers++;
+    }
 
-    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; }
+    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) {
+        std::scoped_lock lock(mLock);
+        mCounters[id].palms++;
+    }
 
     // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and
     // records it if so.
     void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) {
+        std::scoped_lock lock(mLock);
         switch (gesture.type) {
             case kGestureTypeFling:
                 if (gesture.details.fling.fling_state == GESTURES_FLING_START) {
@@ -200,15 +209,20 @@
                                                                  void* cookie) {
         LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
         MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
-        accumulator.produceAtoms(outEventList);
-        accumulator.resetCounters();
+        accumulator.produceAtomsAndReset(*outEventList);
         return AStatsManager_PULL_SUCCESS;
     }
 
-    void produceAtoms(AStatsEventList* outEventList) const {
+    void produceAtomsAndReset(AStatsEventList& outEventList) {
+        std::scoped_lock lock(mLock);
+        produceAtomsLocked(outEventList);
+        resetCountersLocked();
+    }
+
+    void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) {
         for (auto& [id, counters] : mCounters) {
             auto [busId, vendorId, productId, versionId] = id;
-            addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
+            addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
                            versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false),
                            counters.fingers, counters.palms, counters.twoFingerSwipeGestures,
                            counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
@@ -216,7 +230,7 @@
         }
     }
 
-    void resetCounters() { mCounters.clear(); }
+    void resetCountersLocked() REQUIRES(mLock) { mCounters.clear(); }
 
     // Stores the counters for a specific touchpad model. Fields have the same meanings as those of
     // the TouchpadUsage atom; see that definition for detailed documentation.
@@ -232,7 +246,10 @@
 
     // Metrics are aggregated by device model and version, so if two devices of the same model and
     // version are connected at once, they will have the same counters.
-    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters;
+    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters GUARDED_BY(mLock);
+
+    // Metrics are pulled by a binder thread, so we need to guard them with a mutex.
+    mutable std::mutex mLock;
 };
 
 } // namespace
@@ -246,7 +263,8 @@
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
-        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
+        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())),
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
     RawAbsoluteAxisInfo slotAxisInfo;
     deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
     if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
@@ -331,31 +349,56 @@
 
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
         mDisplayId = ADISPLAY_ID_NONE;
-        if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+        std::optional<DisplayViewport> resolvedViewport;
+        std::optional<FloatRect> boundsInLogicalDisplay;
+        if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
             // This InputDevice is associated with a viewport.
             // Only generate events for the associated display.
-            const bool mismatchedPointerDisplay =
-                    (viewport->displayId != mPointerController->getDisplayId());
-            if (mismatchedPointerDisplay) {
-                ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
-                      "controller",
-                      mDeviceContext.getName().c_str());
+            mDisplayId = assocViewport->displayId;
+            resolvedViewport = *assocViewport;
+            if (!mEnablePointerChoreographer) {
+                const bool mismatchedPointerDisplay =
+                        (assocViewport->displayId != mPointerController->getDisplayId());
+                if (mismatchedPointerDisplay) {
+                    ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
+                          "controller",
+                          mDeviceContext.getName().c_str());
+                    mDisplayId.reset();
+                }
             }
-            mDisplayId = mismatchedPointerDisplay ? std::nullopt
-                                                  : std::make_optional(viewport->displayId);
         } else {
             // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-            mDisplayId = mPointerController->getDisplayId();
-        }
-
-        ui::Rotation orientation = ui::ROTATION_0;
-        if (mDisplayId.has_value()) {
-            if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
-                orientation = getInverseRotation(viewport->orientation);
+            if (mEnablePointerChoreographer) {
+                // Always use DISPLAY_ID_NONE for touchpad events.
+                // PointerChoreographer will make it target the correct the displayId later.
+                resolvedViewport =
+                        getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+                mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+            } else {
+                mDisplayId = mPointerController->getDisplayId();
+                if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+                    resolvedViewport = *v;
+                }
+                if (auto bounds = mPointerController->getBounds(); bounds) {
+                    boundsInLogicalDisplay = *bounds;
+                }
             }
         }
+
         mGestureConverter.setDisplayId(mDisplayId);
-        mGestureConverter.setOrientation(orientation);
+        mGestureConverter.setOrientation(resolvedViewport
+                                                 ? getInverseRotation(resolvedViewport->orientation)
+                                                 : ui::ROTATION_0);
+
+        if (!boundsInLogicalDisplay) {
+            boundsInLogicalDisplay = resolvedViewport
+                    ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+                                static_cast<float>(resolvedViewport->logicalTop),
+                                static_cast<float>(resolvedViewport->logicalRight - 1),
+                                static_cast<float>(resolvedViewport->logicalBottom - 1)}
+                    : FloatRect{0, 0, 0, 0};
+        }
+        mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay);
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
@@ -423,6 +466,9 @@
     if (mPointerCaptured) {
         return mCapturedEventConverter.process(*rawEvent);
     }
+    if (mMotionAccumulator.getActiveSlotsCount() == 0) {
+        mGestureStartTime = rawEvent->when;
+    }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
         updatePalmDetectionMetrics();
@@ -488,7 +534,7 @@
     if (mDisplayId) {
         MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
         for (Gesture& gesture : mGesturesToProcess) {
-            out += mGestureConverter.handleGesture(when, readTime, gesture);
+            out += mGestureConverter.handleGesture(when, readTime, mGestureStartTime, gesture);
             metricsAccumulator.processGesture(mMetricsId, gesture);
         }
     }
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index a68ae43..897edca 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -107,10 +107,14 @@
     // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
     std::set<int32_t> mPalmTrackingIds;
 
+    const bool mEnablePointerChoreographer;
+
     // The display that events generated by this mapper should target. This can be set to
     // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
     std::optional<int32_t> mDisplayId;
+
+    nsecs_t mGestureStartTime{0};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index f70be72..b0fc903 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -152,6 +152,14 @@
     }
 }
 
+size_t MultiTouchMotionAccumulator::getActiveSlotsCount() const {
+    if (!mUsingSlotsProtocol) {
+        return mCurrentSlot < 0 ? 0 : mCurrentSlot;
+    }
+    return std::count_if(mSlots.begin(), mSlots.end(),
+                         [](const Slot& slot) { return slot.mInUse; });
+}
+
 // --- MultiTouchMotionAccumulator::Slot ---
 
 ToolType MultiTouchMotionAccumulator::Slot::getToolType() const {
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 943dde5..0e3e2bb 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -77,6 +77,7 @@
     void process(const RawEvent* rawEvent);
     void finishSync();
 
+    size_t getActiveSlotsCount() const;
     inline size_t getSlotCount() const { return mSlots.size(); }
     inline const Slot& getSlot(size_t index) const {
         LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 7006e9e..19788ce 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -20,6 +20,7 @@
 #include <sstream>
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -28,10 +29,20 @@
 #include "TouchCursorInputMapperCommon.h"
 #include "input/Input.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
 
+// This will disable the tap to click while the user is typing on a physical keyboard
+const bool ENABLE_TOUCHPAD_PALM_REJECTION = input_flags::enable_touchpad_typing_palm_rejection();
+
+// In addition to v1, v2 will also cancel ongoing move gestures while typing and add delay in
+// re-enabling the tap to click.
+const bool ENABLE_TOUCHPAD_PALM_REJECTION_V2 =
+        input_flags::enable_v2_touchpad_typing_palm_rejection();
+
 uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
     switch (gesturesButton) {
         case GESTURES_BUTTON_LEFT:
@@ -55,7 +66,8 @@
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mPointerController(readerContext.getPointerController(deviceId)) {
+        mPointerController(readerContext.getPointerController(deviceId)),
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
     deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
 }
@@ -69,6 +81,8 @@
     out << StringPrintf("Button state: 0x%08x\n", mButtonState);
     out << "Down time: " << mDownTime << "\n";
     out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
+    out << "Is hovering: " << mIsHovering << "\n";
+    out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n";
     return out.str();
 }
 
@@ -76,7 +90,7 @@
     std::list<NotifyArgs> out;
     switch (mCurrentClassification) {
         case MotionClassification::TWO_FINGER_SWIPE:
-            out.push_back(endScroll(when, when));
+            out += endScroll(when, when);
             break;
         case MotionClassification::MULTI_FINGER_SWIPE:
             out += handleMultiFingerSwipeLift(when, when);
@@ -103,11 +117,11 @@
 void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
     info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
 
-    // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture
-    // is enabled.
-    if (std::optional<FloatRect> rect = mPointerController->getBounds(); rect.has_value()) {
-        info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0);
-        info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0);
+    if (!mBoundsInLogicalDisplay.isEmpty()) {
+        info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left,
+                            mBoundsInLogicalDisplay.right, 0, 0, 0);
+        info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, mBoundsInLogicalDisplay.top,
+                            mBoundsInLogicalDisplay.bottom, 0, 0, 0);
     }
 
     info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0);
@@ -123,6 +137,7 @@
 }
 
 std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture) {
     if (!mDisplayId) {
         // Ignore gestures when there is no target display configured.
@@ -131,13 +146,13 @@
 
     switch (gesture.type) {
         case kGestureTypeMove:
-            return {handleMove(when, readTime, gesture)};
+            return handleMove(when, readTime, gestureStartTime, gesture);
         case kGestureTypeButtonsChange:
             return handleButtonsChange(when, readTime, gesture);
         case kGestureTypeScroll:
             return handleScroll(when, readTime, gesture);
         case kGestureTypeFling:
-            return handleFling(when, readTime, gesture);
+            return handleFling(when, readTime, gestureStartTime, gesture);
         case kGestureTypeSwipe:
             return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
                                           gesture.details.swipe.dy);
@@ -154,34 +169,66 @@
     }
 }
 
-NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture) {
+std::list<NotifyArgs> GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
-    if (std::abs(deltaX) > 0 || std::abs(deltaY) > 0) {
-        enableTapToClick();
+    const auto [oldXCursorPosition, oldYCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        bool wasHoverCancelled = mIsHoverCancelled;
+        // Gesture will be cancelled if it started before the user started typing and
+        // there is a active IME connection.
+        mIsHoverCancelled = gestureStartTime <= mReaderContext.getLastKeyDownTimestamp() &&
+                mReaderContext.getPolicy()->isInputMethodConnectionActive();
+
+        if (!wasHoverCancelled && mIsHoverCancelled) {
+            // This is the first event of the cancelled gesture, we won't return because we need to
+            // generate a HOVER_EXIT event
+            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+            return exitHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
+        } else if (mIsHoverCancelled) {
+            return {};
+        }
     }
+
     rotateDelta(mOrientation, &deltaX, &deltaY);
 
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->move(deltaX, deltaY);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    // Update the cursor, and enable tap to click if the gesture is not cancelled
+    if (!mIsHoverCancelled) {
+        // handleFling calls hoverMove with zero delta on FLING_TAP_DOWN. Don't enable tap to click
+        // for this case as subsequent handleButtonsChange may choose to ignore this tap.
+        if ((ENABLE_TOUCHPAD_PALM_REJECTION || ENABLE_TOUCHPAD_PALM_REJECTION_V2) &&
+            (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
+            enableTapToClick(when);
+        }
+        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+        mPointerController->move(deltaX, deltaY);
+        mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    std::list<NotifyArgs> out;
+    const bool down = isPointerDown(mButtonState);
+    if (!down) {
+        out += enterHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
+    }
+    const auto [newXCursorPosition, newYCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, newXCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, newYCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
-    const bool down = isPointerDown(mButtonState);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
     const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
-    return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
-                          /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
-                          yCursorPosition);
+    out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                                 /*pointerCount=*/1, &coords, newXCursorPosition,
+                                 newYCursorPosition));
+    return out;
 }
 
 std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
@@ -191,7 +238,8 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -200,8 +248,15 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
 
-    if (mReaderContext.isPreventingTouchpadTaps()) {
-        enableTapToClick();
+    // V2 palm rejection should override V1
+    if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
+        enableTapToClick(when);
+        if (gesture.details.buttons.is_tap && when <= mWhenToEnableTapToClick) {
+            // return early to prevent this tap
+            return out;
+        }
+    } else if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
+        enableTapToClick(when);
         if (gesture.details.buttons.is_tap) {
             // return early to prevent this tap
             return out;
@@ -222,16 +277,16 @@
             newButtonState |= actionButton;
             pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                  actionButton, newButtonState,
-                                                 /* pointerCount= */ 1, mFingerProps.data(),
-                                                 &coords, xCursorPosition, yCursorPosition));
+                                                 /*pointerCount=*/1, &coords, xCursorPosition,
+                                                 yCursorPosition));
         }
     }
     if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
         mDownTime = when;
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     &coords, xCursorPosition, yCursorPosition));
     }
     out.splice(out.end(), pressEvents);
 
@@ -247,20 +302,16 @@
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         &coords, xCursorPosition, yCursorPosition));
         }
     }
     if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
-        // Send a HOVER_MOVE to tell the application that the mouse is hovering again.
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                                     /*actionButton=*/0, newButtonState, /*pointerCount=*/1,
-                                     mFingerProps.data(), &coords, xCursorPosition,
-                                     yCursorPosition));
+                                     newButtonState, /* pointerCount= */ 1, &coords,
+                                     xCursorPosition, yCursorPosition));
+        mButtonState = newButtonState;
+        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     }
     mButtonState = newButtonState;
     return out;
@@ -268,7 +319,8 @@
 
 std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
@@ -284,18 +336,18 @@
         if (mButtonState & button) {
             newButtonState &= ~button;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         button, newButtonState, /*pointerCount=*/1,
-                                         mFingerProps.data(), &coords, xCursorPosition,
-                                         yCursorPosition));
+                                         button, newButtonState, /*pointerCount=*/1, &coords,
+                                         xCursorPosition, yCursorPosition));
         }
     }
+    mButtonState = 0;
     if (pointerDown) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                     newButtonState, /*pointerCount=*/1, mFingerProps.data(),
-                                     &coords, xCursorPosition, yCursorPosition));
+                                     mButtonState, /*pointerCount=*/1, &coords, xCursorPosition,
+                                     yCursorPosition));
+        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     }
-    mButtonState = 0;
     return out;
 }
 
@@ -303,8 +355,11 @@
                                                      const Gesture& gesture) {
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
@@ -312,8 +367,8 @@
         mDownTime = when;
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
-                               mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                               mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                               mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                               xCursorPosition, yCursorPosition);
         args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
         out.push_back(args);
     }
@@ -328,14 +383,15 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition);
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     return out;
 }
 
 std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture) {
     switch (gesture.details.fling.fling_state) {
         case GESTURES_FLING_START:
@@ -344,7 +400,7 @@
                 // ensure consistency between touchscreen and touchpad flings), so we're just using
                 // the "start fling" gestures as a marker for the end of a two-finger scroll
                 // gesture.
-                return {endScroll(when, readTime)};
+                return endScroll(when, readTime);
             }
             break;
         case GESTURES_FLING_TAP_DOWN:
@@ -354,13 +410,10 @@
                 // magnitude, which will also result in the pointer icon being updated.
                 // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been
                 //  initiated with a touchpad.
-                if (!mReaderContext.isPreventingTouchpadTaps()) {
-                    enableTapToClick();
-                }
-                return {handleMove(when, readTime,
-                                   Gesture(kGestureMove, gesture.start_time, gesture.end_time,
-                                           /*dx=*/0.f,
-                                           /*dy=*/0.f))};
+                return handleMove(when, readTime, gestureStartTime,
+                                  Gesture(kGestureMove, gesture.start_time, gesture.end_time,
+                                          /*dx=*/0.f,
+                                          /*dy=*/0.f));
             }
             break;
         default:
@@ -370,17 +423,21 @@
     return {};
 }
 
-NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+std::list<NotifyArgs> GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+    std::list<NotifyArgs> out;
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition);
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition);
     args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
-    return args;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+    return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
@@ -389,13 +446,18 @@
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
 
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
         // three and then put a fourth finger down), the gesture library will treat it as two
         // separate swipes with an appropriate lift event between them, so we don't have to worry
         // about the finger count changing mid-swipe.
+
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
+
         mSwipeFingerCount = fingerCount;
 
         constexpr float FAKE_FINGER_SPACING = 100;
@@ -414,16 +476,14 @@
                                           fingerCount);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         for (size_t i = 1; i < mSwipeFingerCount; i++) {
             out.push_back(makeMotionArgs(when, readTime,
                                          AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                  (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                          /* actionButton= */ 0, mButtonState,
-                                         /* pointerCount= */ i + 1, mFingerProps.data(),
-                                         mFakeFingerCoords.data(), xCursorPosition,
-                                         yCursorPosition));
+                                         /* pointerCount= */ i + 1, mFakeFingerCoords.data(),
+                                         xCursorPosition, yCursorPosition));
         }
     }
     float rotatedDeltaX = dx, rotatedDeltaY = -dy;
@@ -441,8 +501,7 @@
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                                  mButtonState, /* pointerCount= */ mSwipeFingerCount,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     return out;
 }
 
@@ -452,7 +511,8 @@
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         return out;
     }
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
 
@@ -461,22 +521,22 @@
                                      AMOTION_EVENT_ACTION_POINTER_UP |
                                              ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     }
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
                                  /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0);
     mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     mSwipeFingerCount = 0;
     return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
                                                                   const Gesture& gesture) {
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     // Pinch gesture phases are reported a little differently from others, in that the same details
     // struct is used for all phases of the gesture, just with different zoom_state values. When
@@ -487,6 +547,10 @@
         LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
                             "First pinch gesture does not have the START zoom state (%d instead).",
                             gesture.details.pinch.zoom_state);
+        std::list<NotifyArgs> out;
+
+        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+
         mCurrentClassification = MotionClassification::PINCH;
         mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
@@ -499,17 +563,14 @@
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
-        std::list<NotifyArgs> out;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         out.push_back(makeMotionArgs(when, readTime,
                                      AMOTION_EVENT_ACTION_POINTER_DOWN |
                                              1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
-                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                     yCursorPosition));
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
         return out;
     }
 
@@ -527,33 +588,67 @@
                                       xCursorPosition + mPinchFingerSeparation / 2);
     mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
     return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0,
-                           mButtonState, /*pointerCount=*/2, mFingerProps.data(),
-                           mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)};
+                           mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data(),
+                           xCursorPosition, yCursorPosition)};
 }
 
 std::list<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
+    const auto [xCursorPosition, yCursorPosition] =
+            mEnablePointerChoreographer ? FloatPoint{0, 0} : mPointerController->getPosition();
 
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
     out.push_back(makeMotionArgs(when, readTime,
                                  AMOTION_EVENT_ACTION_POINTER_UP |
                                          1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                  /*actionButton=*/0, mButtonState, /*pointerCount=*/2,
-                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
-                                 yCursorPosition));
-    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                 mButtonState, /*pointerCount=*/1, mFingerProps.data(),
                                  mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
-    mCurrentClassification = MotionClassification::NONE;
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
+                                 mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data(),
+                                 xCursorPosition, yCursorPosition));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+    mCurrentClassification = MotionClassification::NONE;
+    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime,
+                                                   float xCursorPosition, float yCursorPosition) {
+    if (!mIsHovering) {
+        mIsHovering = true;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER, xCursorPosition,
+                               yCursorPosition)};
+    } else {
+        return {};
+    }
+}
+
+std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime,
+                                                  float xCursorPosition, float yCursorPosition) {
+    if (mIsHovering) {
+        mIsHovering = false;
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT, xCursorPosition,
+                               yCursorPosition)};
+    } else {
+        return {};
+    }
+}
+
+NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
+                                                  float xCursorPosition, float yCursorPosition) {
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+    return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
+                          /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition);
+}
+
 NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
-                                                  const PointerProperties* pointerProperties,
                                                   const PointerCoords* pointerCoords,
                                                   float xCursorPosition, float yCursorPosition) {
     return {mReaderContext.getNextId(),
@@ -571,7 +666,7 @@
             mCurrentClassification,
             AMOTION_EVENT_EDGE_FLAG_NONE,
             pointerCount,
-            pointerProperties,
+            mFingerProps.data(),
             pointerCoords,
             /* xPrecision= */ 1.0f,
             /* yPrecision= */ 1.0f,
@@ -581,8 +676,11 @@
             /* videoFrames= */ {}};
 }
 
-void GestureConverter::enableTapToClick() {
-    mReaderContext.setPreventingTouchpadTaps(false);
+void GestureConverter::enableTapToClick(nsecs_t when) {
+    if (mReaderContext.isPreventingTouchpadTaps()) {
+        mWhenToEnableTapToClick = when + TAP_ENABLE_DELAY_NANOS.count();
+        mReaderContext.setPreventingTouchpadTaps(false);
+    }
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index e6cf617..07cc56c 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -34,6 +34,13 @@
 
 namespace android {
 
+using std::chrono_literals::operator""ms;
+/**
+ * This duration is decided based on internal team testing, it may be updated after testing with
+ * larger groups
+ */
+constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms;
+
 // Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
 // PointerController calls.
 class GestureConverter {
@@ -48,22 +55,27 @@
 
     void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
 
+    void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; }
+
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      nsecs_t gestureStartTime,
                                                       const Gesture& gesture);
 
 private:
-    [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime,
-                                              const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleMove(nsecs_t when, nsecs_t readTime,
+                                                   nsecs_t gestureStartTime,
+                                                   const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
                                                      const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+                                                    nsecs_t gestureStartTime,
                                                     const Gesture& gesture);
-    [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> endScroll(nsecs_t when, nsecs_t readTime);
 
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
                                                                uint32_t fingerCount, float dx,
@@ -73,20 +85,30 @@
                                                     const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime);
 
+    [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime,
+                                                   float xCursorPosition, float yCursorPosition);
+    [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime,
+                                                  float xCursorPosition, float yCursorPosition);
+
+    NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
+                                    float xCursorPosition, float yCursorPosition);
+
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
-                                    uint32_t pointerCount,
-                                    const PointerProperties* pointerProperties,
-                                    const PointerCoords* pointerCoords, float xCursorPosition,
-                                    float yCursorPosition);
+                                    uint32_t pointerCount, const PointerCoords* pointerCoords,
+                                    float xCursorPosition, float yCursorPosition);
 
-    void enableTapToClick();
+    void enableTapToClick(nsecs_t when);
+    bool mIsHoverCancelled{false};
+    nsecs_t mWhenToEnableTapToClick{0};
 
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
     std::shared_ptr<PointerControllerInterface> mPointerController;
+    const bool mEnablePointerChoreographer;
 
     std::optional<int32_t> mDisplayId;
+    FloatRect mBoundsInLogicalDisplay{};
     ui::Rotation mOrientation = ui::ROTATION_0;
     RawAbsoluteAxisInfo mXAxisInfo;
     RawAbsoluteAxisInfo mYAxisInfo;
@@ -95,6 +117,9 @@
     // button values (AMOTION_EVENT_BUTTON_...).
     uint32_t mButtonState = 0;
     nsecs_t mDownTime = 0;
+    // Whether we are currently in a hover state (i.e. a HOVER_ENTER event has been sent without a
+    // matching HOVER_EXIT).
+    bool mIsHovering = false;
 
     MotionClassification mCurrentClassification = MotionClassification::NONE;
     // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
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/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
index be2bfed..69264f8 100644
--- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -239,7 +239,7 @@
 
 // Helper to std::visit with lambdas.
 template <typename... V>
-struct Visitor : V... {};
+struct Visitor : V... { using V::operator()...; };
 // explicit deduction guide (not needed as of C++20)
 template <typename... V>
 Visitor(V...) -> Visitor<V...>;
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 23c1691..2803805 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -31,13 +31,14 @@
     out: ["inputflinger_bootstrap.rs.h"],
 }
 
-rust_ffi_static {
-    name: "libinputflinger_rs",
+rust_defaults {
+    name: "libinputflinger_rs_defaults",
     crate_name: "inputflinger",
     srcs: ["lib.rs"],
     rustlibs: [
         "libcxx",
         "com.android.server.inputflinger-rust",
+        "android.hardware.input.common-V1-rust",
         "libbinder_rs",
         "liblog_rust",
         "liblogger",
@@ -45,6 +46,24 @@
     host_supported: true,
 }
 
+rust_ffi_static {
+    name: "libinputflinger_rs",
+    defaults: ["libinputflinger_rs_defaults"],
+}
+
+rust_test {
+    name: "libinputflinger_rs_test",
+    defaults: ["libinputflinger_rs_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: ["device_tests"],
+    sanitize: {
+        address: true,
+        hwaddress: true,
+    },
+}
+
 cc_library_headers {
     name: "inputflinger_rs_bootstrap_cxx_headers",
     host_supported: true,
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
new file mode 100644
index 0000000..894b881
--- /dev/null
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Bounce keys input filter implementation.
+//! Bounce keys is an accessibility feature to aid users who have physical disabilities, that
+//! allows the user to configure the device to ignore rapid, repeated key presses of the same key.
+use crate::input_filter::Filter;
+
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug)]
+struct LastUpKeyEvent {
+    keycode: i32,
+    event_time: i64,
+}
+
+#[derive(Debug)]
+struct BlockedEvent {
+    device_id: i32,
+    keycode: i32,
+}
+
+pub struct BounceKeysFilter {
+    next: Box<dyn Filter + Send + Sync>,
+    key_event_map: HashMap<i32, LastUpKeyEvent>,
+    blocked_events: Vec<BlockedEvent>,
+    external_devices: HashSet<i32>,
+    bounce_key_threshold_ns: i64,
+}
+
+impl BounceKeysFilter {
+    /// Create a new BounceKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        bounce_key_threshold_ns: i64,
+    ) -> BounceKeysFilter {
+        Self {
+            next,
+            key_event_map: HashMap::new(),
+            blocked_events: Vec::new(),
+            external_devices: HashSet::new(),
+            bounce_key_threshold_ns,
+        }
+    }
+}
+
+impl Filter for BounceKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        if !(self.external_devices.contains(&event.deviceId) && event.source == Source::KEYBOARD) {
+            self.next.notify_key(event);
+            return;
+        }
+        match event.action {
+            KeyEventAction::DOWN => match self.key_event_map.get(&event.deviceId) {
+                None => self.next.notify_key(event),
+                Some(last_up_event) => {
+                    if event.keyCode == last_up_event.keycode
+                        && event.eventTime < last_up_event.event_time + self.bounce_key_threshold_ns
+                    {
+                        self.blocked_events.push(BlockedEvent {
+                            device_id: event.deviceId,
+                            keycode: event.keyCode,
+                        });
+                        debug!("Event dropped because last up was too recent");
+                    } else {
+                        self.key_event_map.remove(&event.deviceId);
+                        self.next.notify_key(event);
+                    }
+                }
+            },
+            KeyEventAction::UP => {
+                self.key_event_map.insert(
+                    event.deviceId,
+                    LastUpKeyEvent { keycode: event.keyCode, event_time: event.eventTime },
+                );
+                if let Some(index) = self.blocked_events.iter().position(|blocked_event| {
+                    blocked_event.device_id == event.deviceId
+                        && blocked_event.keycode == event.keyCode
+                }) {
+                    self.blocked_events.remove(index);
+                    debug!("Event dropped because key down was already dropped");
+                } else {
+                    self.next.notify_key(event);
+                }
+            }
+            _ => (),
+        }
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        self.key_event_map.retain(|id, _| device_infos.iter().any(|x| *id == x.deviceId));
+        self.blocked_events.retain(|blocked_event| {
+            device_infos.iter().any(|x| blocked_event.device_id == x.deviceId)
+        });
+        self.external_devices.clear();
+        for device_info in device_infos {
+            if device_info.external {
+                self.external_devices.insert(device_info.deviceId);
+            }
+        }
+        self.next.notify_devices_changed(device_infos);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::bounce_keys_filter::BounceKeysFilter;
+    use crate::input_filter::{test_filter::TestFilter, Filter};
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+
+    static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: 1,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 1,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    #[test]
+    fn test_is_notify_key_for_external_keyboard() {
+        let mut next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        next.clear();
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { eventTime: 100, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_doesnt_block_for_internal_keyboard() {
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_doesnt_block_for_external_stylus() {
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+
+        let event =
+            KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event =
+            KeyEvent { action: KeyEventAction::UP, source: Source::STYLUS, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event =
+            KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_for_multiple_external_keyboards() {
+        let mut next = TestFilter::new();
+        let mut filter = setup_filter_with_devices(
+            Box::new(next.clone()),
+            &[
+                DeviceInfo { deviceId: 1, external: true },
+                DeviceInfo { deviceId: 2, external: true },
+            ],
+            100, /* threshold */
+        );
+
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        next.clear();
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { deviceId: 2, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    fn setup_filter_with_external_device(
+        next: Box<dyn Filter + Send + Sync>,
+        device_id: i32,
+        threshold: i64,
+    ) -> BounceKeysFilter {
+        setup_filter_with_devices(
+            next,
+            &[DeviceInfo { deviceId: device_id, external: true }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_internal_device(
+        next: Box<dyn Filter + Send + Sync>,
+        device_id: i32,
+        threshold: i64,
+    ) -> BounceKeysFilter {
+        setup_filter_with_devices(
+            next,
+            &[DeviceInfo { deviceId: device_id, external: false }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_devices(
+        next: Box<dyn Filter + Send + Sync>,
+        devices: &[DeviceInfo],
+        threshold: i64,
+    ) -> BounceKeysFilter {
+        let mut filter = BounceKeysFilter::new(next, threshold);
+        filter.notify_devices_changed(devices);
+        filter
+    }
+}
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
new file mode 100644
index 0000000..e94a71f
--- /dev/null
+++ b/services/inputflinger/rust/input_filter.rs
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! InputFilter manages all the filtering components that can intercept events, modify the events,
+//! block events, etc depending on the situation. This will be used support Accessibility features
+//! like Sticky keys, Slow keys, Bounce keys, etc.
+
+use binder::{Interface, Strong};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo,
+    IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+    InputFilterConfiguration::InputFilterConfiguration,
+    KeyEvent::KeyEvent,
+};
+
+use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::sticky_keys_filter::StickyKeysFilter;
+use log::{error, info};
+use std::sync::{Arc, Mutex, RwLock};
+
+/// Interface for all the sub input filters
+pub trait Filter {
+    fn notify_key(&mut self, event: &KeyEvent);
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+}
+
+struct InputFilterState {
+    first_filter: Box<dyn Filter + Send + Sync>,
+    enabled: bool,
+}
+
+/// The rust implementation of InputFilter
+pub struct InputFilter {
+    // In order to have multiple immutable references to the callbacks that is thread safe need to
+    // wrap the callbacks in Arc<RwLock<...>>
+    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
+    // guarded by Mutex for thread safety
+    state: Mutex<InputFilterState>,
+}
+
+impl Interface for InputFilter {}
+
+impl InputFilter {
+    /// Create a new InputFilter instance.
+    pub fn new(callbacks: Strong<dyn IInputFilterCallbacks>) -> InputFilter {
+        let ref_callbacks = Arc::new(RwLock::new(callbacks));
+        let base_filter = Box::new(BaseFilter::new(ref_callbacks.clone()));
+        Self::create_input_filter(base_filter, ref_callbacks)
+    }
+
+    /// Create test instance of InputFilter
+    fn create_input_filter(
+        first_filter: Box<dyn Filter + Send + Sync>,
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> InputFilter {
+        Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
+    }
+}
+
+impl IInputFilter for InputFilter {
+    fn isEnabled(&self) -> binder::Result<bool> {
+        Result::Ok(self.state.lock().unwrap().enabled)
+    }
+
+    fn notifyKey(&self, event: &KeyEvent) -> binder::Result<()> {
+        let first_filter = &mut self.state.lock().unwrap().first_filter;
+        first_filter.notify_key(event);
+        Result::Ok(())
+    }
+
+    fn notifyInputDevicesChanged(&self, device_infos: &[DeviceInfo]) -> binder::Result<()> {
+        let first_filter = &mut self.state.lock().unwrap().first_filter;
+        first_filter.notify_devices_changed(device_infos);
+        Result::Ok(())
+    }
+
+    fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
+        let mut state = self.state.lock().unwrap();
+        let mut first_filter: Box<dyn Filter + Send + Sync> =
+            Box::new(BaseFilter::new(self.callbacks.clone()));
+        if config.stickyKeysEnabled {
+            first_filter = Box::new(StickyKeysFilter::new(
+                first_filter,
+                ModifierStateListener::new(self.callbacks.clone()),
+            ));
+            state.enabled = true;
+            info!("Sticky keys filter is installed");
+        }
+        if config.bounceKeysThresholdNs > 0 {
+            first_filter =
+                Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+            state.enabled = true;
+            info!("Bounce keys filter is installed");
+        }
+        state.first_filter = first_filter;
+        Result::Ok(())
+    }
+}
+
+struct BaseFilter {
+    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl BaseFilter {
+    fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> BaseFilter {
+        Self { callbacks }
+    }
+}
+
+impl Filter for BaseFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        match self.callbacks.read().unwrap().sendKeyEvent(event) {
+            Ok(_) => (),
+            _ => error!("Failed to send key event back to native C++"),
+        }
+    }
+
+    fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+        // do nothing
+    }
+}
+
+pub struct ModifierStateListener {
+    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl ModifierStateListener {
+    /// Create a new InputFilter instance.
+    pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
+        Self { callbacks }
+    }
+
+    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
+        let _ = self
+            .callbacks
+            .read()
+            .unwrap()
+            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, InputFilter,
+    };
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
+        InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
+        KeyEventAction::KeyEventAction,
+    };
+    use std::sync::{Arc, RwLock};
+
+    #[test]
+    fn test_not_enabled_with_default_filter() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(!result.unwrap());
+    }
+
+    #[test]
+    fn test_notify_key_with_no_filters() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks.clone())));
+        let event = create_key_event();
+        assert!(input_filter.notifyKey(&event).is_ok());
+        assert_eq!(test_callbacks.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_notify_key_with_filter() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
+        let event = create_key_event();
+        assert!(input_filter.notifyKey(&event).is_ok());
+        assert_eq!(test_filter.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_notify_devices_changed() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
+        assert!(input_filter
+            .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
+            .is_ok());
+        assert!(test_filter.is_device_changed_called());
+    }
+
+    #[test]
+    fn test_notify_configuration_changed_enabled_bounce_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 100,
+            stickyKeysEnabled: false,
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    #[test]
+    fn test_notify_configuration_changed_enabled_sticky_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 0,
+            stickyKeysEnabled: true,
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    fn create_key_event() -> KeyEvent {
+        KeyEvent {
+            id: 1,
+            deviceId: 1,
+            downTime: 0,
+            readTime: 0,
+            eventTime: 0,
+            source: Source::KEYBOARD,
+            displayId: 0,
+            policyFlags: 0,
+            action: KeyEventAction::DOWN,
+            flags: 0,
+            keyCode: 0,
+            scanCode: 0,
+            metaState: 0,
+        }
+    }
+}
+
+#[cfg(test)]
+pub mod test_filter {
+    use crate::input_filter::Filter;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, KeyEvent::KeyEvent,
+    };
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestFilterInner {
+        is_device_changed_called: bool,
+        last_event: Option<KeyEvent>,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestFilter(Arc<RwLock<TestFilterInner>>);
+
+    impl TestFilter {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&mut self) -> RwLockWriteGuard<'_, TestFilterInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn last_event(&self) -> Option<KeyEvent> {
+            self.0.read().unwrap().last_event
+        }
+
+        pub fn clear(&mut self) {
+            self.inner().last_event = None
+        }
+
+        pub fn is_device_changed_called(&self) -> bool {
+            self.0.read().unwrap().is_device_changed_called
+        }
+    }
+
+    impl Filter for TestFilter {
+        fn notify_key(&mut self, event: &KeyEvent) {
+            self.inner().last_event = Some(*event);
+        }
+        fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+            self.inner().is_device_changed_called = true;
+        }
+    }
+}
+
+#[cfg(test)]
+pub mod test_callbacks {
+    use binder::Interface;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent,
+    };
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestCallbacksInner {
+        last_modifier_state: u32,
+        last_locked_modifier_state: u32,
+        last_event: Option<KeyEvent>,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestCallbacks(Arc<RwLock<TestCallbacksInner>>);
+
+    impl Interface for TestCallbacks {}
+
+    impl TestCallbacks {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestCallbacksInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn last_event(&self) -> Option<KeyEvent> {
+            self.0.read().unwrap().last_event
+        }
+
+        pub fn clear(&mut self) {
+            self.inner().last_event = None;
+            self.inner().last_modifier_state = 0;
+            self.inner().last_locked_modifier_state = 0;
+        }
+
+        pub fn get_last_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_modifier_state
+        }
+
+        pub fn get_last_locked_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_locked_modifier_state
+        }
+    }
+
+    impl IInputFilterCallbacks for TestCallbacks {
+        fn sendKeyEvent(&self, event: &KeyEvent) -> binder::Result<()> {
+            self.inner().last_event = Some(*event);
+            Result::Ok(())
+        }
+
+        fn onModifierStateChanged(
+            &self,
+            modifier_state: i32,
+            locked_modifier_state: i32,
+        ) -> std::result::Result<(), binder::Status> {
+            self.inner().last_modifier_state = modifier_state as u32;
+            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            Result::Ok(())
+        }
+    }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index 501e435..fa16898 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -19,13 +19,21 @@
 //! We use cxxbridge to create IInputFlingerRust - the Rust component of inputflinger - and
 //! pass it back to C++ as a local AIDL interface.
 
+mod bounce_keys_filter;
+mod input_filter;
+mod sticky_keys_filter;
+
+use crate::input_filter::InputFilter;
 use binder::{
-    unstable_api::{AIBinder, new_spibinder,},
+    unstable_api::{new_spibinder, AIBinder},
     BinderFeatures, Interface, StatusCode, Strong,
 };
-use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputFlingerRust::{
-    BnInputFlingerRust, IInputFlingerRust,
-    IInputFlingerRustBootstrapCallback::IInputFlingerRustBootstrapCallback,
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    IInputFilter::{BnInputFilter, IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+    IInputFlingerRust::{
+        BnInputFlingerRust, IInputFlingerRust,
+        IInputFlingerRustBootstrapCallback::IInputFlingerRustBootstrapCallback,
+    },
 };
 use log::debug;
 
@@ -71,8 +79,8 @@
     // SAFETY: Our caller guaranteed that `callback` is a valid pointer to an `AIBinder` and its
     // reference count has been incremented..
     let Some(callback) = (unsafe { new_spibinder(callback) }) else {
-            panic!("Failed to get SpAIBinder from raw callback pointer");
-        };
+        panic!("Failed to get SpAIBinder from raw callback pointer");
+    };
 
     let callback: Result<Strong<dyn IInputFlingerRustBootstrapCallback>, StatusCode> =
         callback.into_interface();
@@ -93,7 +101,19 @@
 
 impl Interface for InputFlingerRust {}
 
-impl IInputFlingerRust for InputFlingerRust {}
+impl IInputFlingerRust for InputFlingerRust {
+    fn createInputFilter(
+        &self,
+        callbacks: &Strong<dyn IInputFilterCallbacks>,
+    ) -> binder::Result<Strong<dyn IInputFilter>> {
+        debug!("Creating InputFilter");
+        let filter = BnInputFilter::new_binder(
+            InputFilter::new(callbacks.clone()),
+            BinderFeatures::default(),
+        );
+        Result::Ok(filter)
+    }
+}
 
 impl Drop for InputFlingerRust {
     fn drop(&mut self) {
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
new file mode 100644
index 0000000..da581b8
--- /dev/null
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -0,0 +1,515 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Sticky keys input filter implementation.
+//! Sticky keys is an accessibility feature that assists users who have physical disabilities or
+//! helps users reduce repetitive strain injury. It serializes keystrokes instead of pressing
+//! multiple keys at a time, allowing the user to press and release a modifier key, such as Shift,
+//! Ctrl, Alt, or any other modifier key, and have it remain active until any other key is pressed.
+use crate::input_filter::{Filter, ModifierStateListener};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use std::collections::HashSet;
+
+// Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
+const KEYCODE_ALT_LEFT: i32 = 57;
+const KEYCODE_ALT_RIGHT: i32 = 58;
+const KEYCODE_SHIFT_LEFT: i32 = 59;
+const KEYCODE_SHIFT_RIGHT: i32 = 60;
+const KEYCODE_SYM: i32 = 63;
+const KEYCODE_CTRL_LEFT: i32 = 113;
+const KEYCODE_CTRL_RIGHT: i32 = 114;
+const KEYCODE_CAPS_LOCK: i32 = 115;
+const KEYCODE_SCROLL_LOCK: i32 = 116;
+const KEYCODE_META_LEFT: i32 = 117;
+const KEYCODE_META_RIGHT: i32 = 118;
+const KEYCODE_FUNCTION: i32 = 119;
+const KEYCODE_NUM_LOCK: i32 = 143;
+
+// Modifier states: values are from /frameworks/native/include/android/input.h
+const META_ALT_ON: u32 = 0x02;
+const META_ALT_LEFT_ON: u32 = 0x10;
+const META_ALT_RIGHT_ON: u32 = 0x20;
+const META_SHIFT_ON: u32 = 0x01;
+const META_SHIFT_LEFT_ON: u32 = 0x40;
+const META_SHIFT_RIGHT_ON: u32 = 0x80;
+const META_CTRL_ON: u32 = 0x1000;
+const META_CTRL_LEFT_ON: u32 = 0x2000;
+const META_CTRL_RIGHT_ON: u32 = 0x4000;
+const META_META_ON: u32 = 0x10000;
+const META_META_LEFT_ON: u32 = 0x20000;
+const META_META_RIGHT_ON: u32 = 0x40000;
+
+pub struct StickyKeysFilter {
+    next: Box<dyn Filter + Send + Sync>,
+    listener: ModifierStateListener,
+    /// Tracking devices that contributed to the modifier state.
+    contributing_devices: HashSet<i32>,
+    /// State describing the current enabled modifiers. This contain both locked and non-locked
+    /// modifier state bits.
+    modifier_state: u32,
+    /// State describing the current locked modifiers. These modifiers will not be cleared on a
+    /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
+    /// again.
+    locked_modifier_state: u32,
+}
+
+impl StickyKeysFilter {
+    /// Create a new StickyKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        listener: ModifierStateListener,
+    ) -> StickyKeysFilter {
+        Self {
+            next,
+            listener,
+            contributing_devices: HashSet::new(),
+            modifier_state: 0,
+            locked_modifier_state: 0,
+        }
+    }
+}
+
+impl Filter for StickyKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        let up = event.action == KeyEventAction::UP;
+        let mut modifier_state = self.modifier_state;
+        let mut locked_modifier_state = self.locked_modifier_state;
+        if !is_ephemeral_modifier_key(event.keyCode) {
+            // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
+            // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
+            // the KeyEvent.
+            let old_modifier_state = event.metaState as u32;
+            let mut new_event = *event;
+            // Send the current modifier state with the key event before clearing non-locked
+            // modifier state
+            new_event.metaState =
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+            self.next.notify_key(&new_event);
+            if up && !is_modifier_key(event.keyCode) {
+                modifier_state =
+                    clear_ephemeral_modifier_state(modifier_state) | locked_modifier_state;
+            }
+        } else if up {
+            // Update contributing devices to track keyboards
+            self.contributing_devices.insert(event.deviceId);
+            // If ephemeral modifier key, capture the key and update the sticky modifier states
+            let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
+            let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
+            if locked_modifier_state & modifier_key_mask != 0 {
+                locked_modifier_state &= !symmetrical_modifier_key_mask;
+                modifier_state &= !symmetrical_modifier_key_mask;
+            } else if modifier_key_mask & modifier_state != 0 {
+                locked_modifier_state |= modifier_key_mask;
+                modifier_state =
+                    (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
+            } else {
+                modifier_state |= modifier_key_mask;
+            }
+        }
+        if self.modifier_state != modifier_state
+            || self.locked_modifier_state != locked_modifier_state
+        {
+            self.modifier_state = modifier_state;
+            self.locked_modifier_state = locked_modifier_state;
+            self.listener.modifier_state_changed(modifier_state, locked_modifier_state);
+        }
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        // Clear state if all contributing devices removed
+        self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
+        if self.contributing_devices.is_empty()
+            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+        {
+            self.modifier_state = 0;
+            self.locked_modifier_state = 0;
+            self.listener.modifier_state_changed(0, 0);
+        }
+        self.next.notify_devices_changed(device_infos);
+    }
+}
+
+fn is_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+            | KEYCODE_SYM
+            | KEYCODE_FUNCTION
+            | KEYCODE_CAPS_LOCK
+            | KEYCODE_NUM_LOCK
+            | KEYCODE_SCROLL_LOCK
+    )
+}
+
+fn is_ephemeral_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+    )
+}
+
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
+        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
+        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
+        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
+        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
+        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
+        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
+        _ => 0,
+    }
+}
+
+/// Modifier mask including both left and right versions of a modifier key.
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
+            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+        }
+        KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
+            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+        }
+        KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
+            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+        }
+        _ => 0,
+    }
+}
+
+fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+    modifier_state
+        & !(META_ALT_LEFT_ON
+            | META_ALT_RIGHT_ON
+            | META_ALT_ON
+            | META_SHIFT_LEFT_ON
+            | META_SHIFT_RIGHT_ON
+            | META_SHIFT_ON
+            | META_CTRL_LEFT_ON
+            | META_CTRL_RIGHT_ON
+            | META_CTRL_ON
+            | META_META_LEFT_ON
+            | META_META_RIGHT_ON
+            | META_META_ON)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, ModifierStateListener,
+    };
+    use crate::sticky_keys_filter::{
+        StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
+        KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
+        KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
+        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
+        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+    };
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+    use std::sync::{Arc, RwLock};
+
+    static DEVICE_ID: i32 = 1;
+    static KEY_A: i32 = 29;
+    static BASE_KEY_DOWN: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: DEVICE_ID,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 0,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    static BASE_KEY_UP: KeyEvent = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_DOWN };
+
+    #[test]
+    fn test_notify_key_consumes_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_ALT_LEFT,
+            KEYCODE_ALT_RIGHT,
+            KEYCODE_CTRL_LEFT,
+            KEYCODE_CTRL_RIGHT,
+            KEYCODE_SHIFT_LEFT,
+            KEYCODE_SHIFT_RIGHT,
+            KEYCODE_META_LEFT,
+            KEYCODE_META_RIGHT,
+        ];
+        for key_code in key_codes.iter() {
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN });
+            assert!(test_filter.last_event().is_none());
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_UP });
+            assert!(test_filter.last_event().is_none());
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_ephemeral_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_CAPS_LOCK,
+            KEYCODE_NUM_LOCK,
+            KEYCODE_SCROLL_LOCK,
+            KEYCODE_FUNCTION,
+            KEYCODE_SYM,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_modifier_keys() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_modifier_state_updated_on_modifier_key_press() {
+        let mut test_filter = TestFilter::new();
+        let mut test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let test_states = &[
+            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
+            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
+            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
+            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
+            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
+            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
+            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
+            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+        ];
+        for test_state in test_states.iter() {
+            test_filter.clear();
+            test_callbacks.clear();
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            // Re-send keys to lock it
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            // Re-send keys to clear
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        }
+    }
+
+    #[test]
+    fn test_modifier_state_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    #[test]
+    fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_key_events_have_sticky_modifier_state() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_modifier_state_not_cleared_until_all_devices_removed() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_devices_changed(&[]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    fn setup_filter(
+        next: Box<dyn Filter + Send + Sync>,
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> StickyKeysFilter {
+        StickyKeysFilter::new(next, ModifierStateListener::new(callbacks))
+    }
+}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 64e8825..db31ded 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -58,6 +58,7 @@
         "InputReader_test.cpp",
         "InstrumentedInputReader.cpp",
         "LatencyTracker_test.cpp",
+        "MultiTouchMotionAccumulator_test.cpp",
         "NotifyArgs_test.cpp",
         "PointerChoreographer_test.cpp",
         "PreferStylusOverTouch_test.cpp",
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index b55c9cc..f3a6f01 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -16,13 +16,24 @@
 
 #include "CursorInputMapper.h"
 
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
 #include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
 
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
+#include "NotifyArgs.h"
 #include "TestEventMatchers.h"
+#include "ui/Rotation.h"
 
 #define TAG "CursorInputMapper_test"
 
@@ -38,18 +49,30 @@
 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 SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
+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
+
+constexpr int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+namespace input_flags = com::android::input::flags;
 
 /**
  * Unit tests for CursorInputMapper.
- * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing
- * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name
- * can be simplified to 'CursorInputMapperTest'.
- * TODO(b/283812079): move CursorInputMapper tests here.
+ * These classes are named 'CursorInputMapperUnitTest...' to avoid name collision with the existing
+ * 'CursorInputMapperTest...' classes. If all of the CursorInputMapper tests are migrated here, the
+ * name can be simplified to 'CursorInputMapperTest'.
+ *
+ * TODO(b/283812079): move the remaining CursorInputMapper tests here. The ones that are left all
+ *   depend on viewport association, for which we'll need to fake InputDeviceContext.
  */
-class CursorInputMapperUnitTest : public InputMapperUnitTest {
+class CursorInputMapperUnitTestBase : public InputMapperUnitTest {
 protected:
-    void SetUp() override {
-        InputMapperUnitTest::SetUp();
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
 
         // Current scan code state - all keys are UP by default
         setScanCodeState(KeyState::UP,
@@ -60,6 +83,14 @@
         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);
+    }
+
+    void createMapper() {
+        createDevice();
         mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 
@@ -71,19 +102,42 @@
                 mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                      InputReaderConfiguration::Change::POINTER_CAPTURE);
         ASSERT_THAT(args,
-                    ElementsAre(
-                            VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+                    ElementsAre(VariantWith<NotifyDeviceResetArgs>(
+                            AllOf(WithDeviceId(DEVICE_ID), WithEventTime(ARBITRARY_TIME)))));
 
         // Check that generation also got bumped
         ASSERT_GT(mDevice->getGeneration(), generation);
     }
 };
 
+class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsMouseInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mMapper->getSources());
+}
+
+TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsTrackballInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mMapper->getSources());
+}
+
 /**
  * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering
  * ends. Currently, it is not.
  */
 TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) {
+    createMapper();
     std::list<NotifyArgs> args;
 
     // Move the cursor a little
@@ -127,6 +181,7 @@
  * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
  */
 TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+    createMapper();
     setPointerCapture(true);
     std::list<NotifyArgs> args;
 
@@ -139,6 +194,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 +234,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 +252,982 @@
     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)))));
+}
+
+TEST_F(CursorInputMapperUnitTest,
+       PopulateDeviceInfoReturnsRangeFromPointerControllerInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    mFakePolicy->clearViewports();
+    mFakePointerController->clearBounds();
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    // Initially there should not be a valid motion range because there's no viewport or pointer
+    // bounds.
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the bounds are set, then there should be a valid motion range.
+    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(systemTime(), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    InputDeviceInfo info2;
+    mMapper->populateDeviceInfo(info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 1,
+                                              800 - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 2,
+                                              480 - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
+                                              -1.0f, 1.0f, 0.0f,
+                                              1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_TRACKBALL, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldSetAllFieldsAndIncludeGlobalMetaState) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState())
+            .WillRepeatedly(Return(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON));
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPointerCount(1), WithPointerId(0, 0),
+                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                                          WithPressure(1.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+    args.clear();
+
+    // Button release.  Should have same down time.
+    args += process(ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
+                                          WithDeviceId(DEVICE_ID),
+                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
+                                          WithEdgeFlags(0), WithPolicyFlags(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                                          WithButtonState(0), WithPointerCount(1),
+                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
+                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                                        TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithDownTime(ARBITRARY_TIME)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentXYUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Motion in X but not Y.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f),
+                              WithPressure(0.0f)))));
+    args.clear();
+
+    // Motion in Y but not X.
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Button press.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    // Button release.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleCombinedXYAndButtonUpdates) {
+    mPropertyMap.addProperty("cursor.mode", "navigation");
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Combined X, Y and Button.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, -2);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                                          WithPressure(1.0f)))));
+    args.clear();
+
+    // Move X, Y a bit while pressed.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 2);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+                                         1.0f / TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithPressure(1.0f)))));
+    args.clear();
+
+    // Release Button.
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtons) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    // press BTN_LEFT, release BTN_LEFT
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f)))));
+    args.clear();
+
+    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
+}
+
+class CursorInputMapperButtonKeyTest
+      : public CursorInputMapperUnitTest,
+        public testing::WithParamInterface<
+                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
+                           int32_t /*expectedKeyCode*/>> {};
+
+TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKey) {
+    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                                             WithKeyCode(expectedKeyCode))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                                             WithKeyCode(expectedKeyCode)))));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTest,
+        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
+                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
+                                        AKEYCODE_FORWARD)));
+
+TEST_F(CursorInputMapperUnitTest, ProcessShouldMoveThePointerAroundInPointerMode) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(110.0f, 220.0f), WithPressure(0.0f), WithSize(0.0f),
+                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
+                              WithOrientation(0.0f), WithDistance(0.0f)))));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
+}
+
+/**
+ * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
+ * pointer acceleration or speed processing should not be applied.
+ */
+TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
+    mReaderConfiguration.pointerVelocityControlParameters = testParams;
+    mFakePolicy->setVelocityControlParams(testParams);
+    createMapper();
+
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+    NotifyMotionArgs motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(10, 20)))));
+}
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+class CursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    mFakePolicy->clearViewports();
+    mFakePointerController->clearBounds();
+    createMapper();
+
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    // Initially there should not be a valid motion range because there's no viewport or pointer
+    // bounds.
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+    // When the viewport and the default pointer display ID is set, then there should be a valid
+    // motion range.
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(systemTime(), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    InputDeviceInfo info2;
+    mMapper->populateDeviceInfo(info2);
+
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_WIDTH - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
+                                              DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
+    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    // press BTN_LEFT, release BTN_LEFT
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f)))));
+    args.clear();
+
+    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
+                                                          AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithButtonState(0),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+}
+
+class CursorInputMapperButtonKeyTestWithChoreographer
+      : public CursorInputMapperUnitTestWithChoreographer,
+        public testing::WithParamInterface<
+                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
+                           int32_t /*expectedKeyCode*/>> {};
+
+TEST_P(CursorInputMapperButtonKeyTestWithChoreographer,
+       ProcessShouldHandleButtonKeyWithZeroCoords) {
+    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                                             WithKeyCode(expectedKeyCode))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithButtonState(expectedButtonState),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
+                                          WithPressure(0.0f))),
+                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                                             WithKeyCode(expectedKeyCode)))));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTestWithChoreographer,
+        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
+                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
+                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
+                                        AKEYCODE_FORWARD)));
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    std::list<NotifyArgs> args;
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(0.0f, 0.0f), WithPressure(0.0f), WithSize(0.0f),
+                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
+                              WithOrientation(0.0f), WithDistance(0.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
+    mReaderConfiguration.pointerVelocityControlParameters = testParams;
+    mFakePolicy->setVelocityControlParams(testParams);
+    createMapper();
+
+    NotifyMotionArgs motionArgs;
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))));
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_EQ(10, relX2);
+    ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_90,
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    ui::ROTATION_0, /*isActive=*/true, "local:1", NO_PORT,
+                                    ViewportType::EXTERNAL);
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+
+    createMapper();
+
+    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    mFakePointerController->setPosition(100, 200);
+
+    // Ensure input events are generated without display ID or coords, because they will be decided
+    // later by PointerChoreographer.
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
+                              WithCoords(0.0f, 0.0f)))));
+}
+
+namespace {
+
+// Minimum timestamp separation between subsequent input events from a Bluetooth device.
+constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
+// Maximum smoothing time delta so that we don't generate events too far into the future.
+constexpr nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
+
+} // namespace
+
+class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        SetUpWithBus(BUS_BLUETOOTH);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+};
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events that come in quick succession, according to their timestamps.
+    for (int i = 0; i < 3; i++) {
+        constexpr static nsecs_t delta = ms2ns(1);
+        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+        kernelEventTime += delta;
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningIsCapped) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events with the same timestamp from the kernel.
+    // Ensure that we do not generate events too far into the future.
+    constexpr static int32_t numEvents =
+            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    for (int i = 0; i < numEvents; i++) {
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+
+    // By processing more events with the same timestamp, we should not generate events with a
+    // timestamp that is more than the specified max time delta from the timestamp at its injection.
+    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+    for (int i = 0; i < 3; i++) {
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(cappedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmootheningNotUsed) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+    // smoothening is not needed, its timestamp is not affected.
+    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+    expectedEventTime = kernelEventTime;
+
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+}
+
+// --- BluetoothCursorInputMapperUnitTestWithChoreographer ---
+
+class BluetoothCursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        SetUpWithBus(BUS_BLUETOOTH);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+};
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmoothening) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events that come in quick succession, according to their timestamps.
+    for (int i = 0; i < 3; i++) {
+        constexpr static nsecs_t delta = ms2ns(1);
+        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+        kernelEventTime += delta;
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningIsCapped) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // Process several events with the same timestamp from the kernel.
+    // Ensure that we do not generate events too far into the future.
+    constexpr static int32_t numEvents =
+            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    for (int i = 0; i < numEvents; i++) {
+        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(expectedEventTime)))));
+        argsList.clear();
+    }
+
+    // By processing more events with the same timestamp, we should not generate events with a
+    // timestamp that is more than the specified max time delta from the timestamp at its injection.
+    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+    for (int i = 0; i < 3; i++) {
+        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
+        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+        EXPECT_THAT(argsList,
+                    ElementsAre(VariantWith<NotifyMotionArgs>(
+                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                  WithEventTime(cappedEventTime)))));
+        argsList.clear();
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningNotUsed) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+    std::list<NotifyArgs> argsList;
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
+
+    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+    // smoothening is not needed, its timestamp is not affected.
+    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+    expectedEventTime = kernelEventTime;
+
+    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
+    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(argsList,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithEventTime(expectedEventTime)))));
+    argsList.clear();
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeApplicationHandle.h b/services/inputflinger/tests/FakeApplicationHandle.h
new file mode 100644
index 0000000..2f634d5
--- /dev/null
+++ b/services/inputflinger/tests/FakeApplicationHandle.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/properties.h>
+#include <android/os/IInputConstants.h>
+#include <gui/InputApplication.h>
+
+namespace android {
+
+namespace inputdispatcher {
+
+class FakeApplicationHandle : public InputApplicationHandle {
+public:
+    FakeApplicationHandle() {
+        static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+                android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+                android::base::HwTimeoutMultiplier());
+        mInfo.name = "Fake Application";
+        mInfo.token = sp<BBinder>::make();
+        mInfo.dispatchingTimeoutMillis =
+                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
+    }
+    virtual ~FakeApplicationHandle() {}
+
+    bool updateInfo() override { return true; }
+
+    void setDispatchingTimeout(std::chrono::milliseconds timeout) {
+        mInfo.dispatchingTimeoutMillis = timeout.count();
+    }
+};
+
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
new file mode 100644
index 0000000..fb2db06
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include "InputDispatcherPolicyInterface.h"
+
+namespace android {
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+public:
+    FakeInputDispatcherPolicy() = default;
+    virtual ~FakeInputDispatcherPolicy() = default;
+
+private:
+    void notifyConfigurationChanged(nsecs_t) override {}
+
+    void notifyNoFocusedWindowAnr(
+            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
+        LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
+    }
+
+    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
+                                  const std::string& reason) override {
+        LOG(ERROR) << "Window is not responding: " << reason;
+    }
+
+    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                std::optional<gui::Pid> pid) override {}
+
+    void notifyInputChannelBroken(const sp<IBinder>&) override {}
+
+    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
+
+    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+                           const std::vector<float>& values) override {}
+
+    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
+                              InputDeviceSensorAccuracy accuracy) override {}
+
+    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
+
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
+        return true; // dispatch event normally
+    }
+
+    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
+
+    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
+
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
+        return 0;
+    }
+
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+                                                 uint32_t) override {
+        return {};
+    }
+
+    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
+
+    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
+
+    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+
+    void setPointerCapture(const PointerCaptureRequest&) override {}
+
+    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
+
+    void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 41c98ef..88f514f 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -261,4 +261,17 @@
     mStylusGestureNotified = deviceId;
 }
 
+std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay(
+        int32_t associatedDisplayId) {
+    if (associatedDisplayId == ADISPLAY_ID_NONE) {
+        associatedDisplayId = mConfig.defaultPointerDisplayId;
+    }
+    for (auto& viewport : mViewports) {
+        if (viewport.displayId == associatedDisplayId) {
+            return std::make_optional(viewport);
+        }
+    }
+    return std::nullopt;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 48912a6..4ef9c3e 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -79,6 +79,8 @@
     void setStylusPointerIconEnabled(bool enabled);
     void setIsInputMethodConnectionActive(bool active);
     bool isInputMethodConnectionActive() override;
+    std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+            int32_t associatedDisplayId) override;
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index ca517f3..31e1173 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -28,6 +28,10 @@
     mMaxY = maxY;
 }
 
+void FakePointerController::clearBounds() {
+    mHaveBounds = false;
+}
+
 const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
     return mSpotsByDisplay;
 }
@@ -42,11 +46,35 @@
 }
 
 int32_t FakePointerController::getDisplayId() const {
-    return mDisplayId;
+    if (!mDisplayId) {
+        return ADISPLAY_ID_NONE;
+    }
+    return *mDisplayId;
 }
 
 void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
     mDisplayId = viewport.displayId;
+    setBounds(viewport.logicalLeft, viewport.logicalTop, viewport.logicalRight - 1,
+              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);
+}
+
+void FakePointerController::assertViewportNotSet() {
+    ASSERT_EQ(std::nullopt, mDisplayId);
 }
 
 void FakePointerController::assertPosition(float x, float y) {
@@ -55,6 +83,32 @@
     ASSERT_NEAR(y, actualY, 1);
 }
 
+void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+    auto it = mSpotsByDisplay.find(displayId);
+    ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
+    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 c374267..061ae62 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -24,25 +24,40 @@
 
 namespace android {
 
+struct SpriteIcon {
+    PointerIconStyle style;
+};
+
 class FakePointerController : public PointerControllerInterface {
 public:
     virtual ~FakePointerController() {}
 
     void setBounds(float minX, float minY, float maxX, float maxY);
+    void clearBounds();
     const std::map<int32_t, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
     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 fade(Transition) 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:
+    std::string dump() override { return ""; }
     std::optional<FloatRect> getBounds() const override;
     void move(float deltaX, float deltaY) override;
-    void fade(Transition) override;
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
@@ -52,8 +67,10 @@
     bool mHaveBounds{false};
     float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
     float mX{0}, mY{0};
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    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/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
new file mode 100644
index 0000000..fe25130
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindowHandle.h
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include "../dispatcher/InputDispatcher.h"
+
+using android::base::Result;
+using android::gui::Pid;
+using android::gui::TouchOcclusionMode;
+using android::gui::Uid;
+using android::gui::WindowInfo;
+using android::gui::WindowInfoHandle;
+
+namespace android {
+namespace inputdispatcher {
+
+namespace {
+
+// The default pid and uid for windows created by the test.
+constexpr gui::Pid WINDOW_PID{999};
+constexpr gui::Uid WINDOW_UID{1001};
+
+static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
+
+} // namespace
+
+class FakeInputReceiver {
+public:
+    std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) {
+        uint32_t consumeSeq = 0;
+        std::unique_ptr<InputEvent> event;
+
+        std::chrono::time_point start = std::chrono::steady_clock::now();
+        status_t result = WOULD_BLOCK;
+        while (result == WOULD_BLOCK) {
+            InputEvent* rawEventPtr = nullptr;
+            result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                       &rawEventPtr);
+            event = std::unique_ptr<InputEvent>(rawEventPtr);
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+            if (elapsed > timeout) {
+                if (timeout != 0ms) {
+                    LOG(ERROR) << "Waited too long for consumer to produce an event, giving up";
+                }
+                break;
+            }
+        }
+        // Events produced by this factory are owned pointers.
+        if (result != OK) {
+            if (timeout == 0ms) {
+                // This is likely expected. No need to log.
+            } else {
+                LOG(ERROR) << "Received result =  " << result << " from consume";
+            }
+            return nullptr;
+        }
+        result = mConsumer.sendFinishedSignal(consumeSeq, true);
+        if (result != OK) {
+            LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal";
+        }
+        return event;
+    }
+
+    explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name)
+          : mConsumer(std::move(channel)) {}
+
+    virtual ~FakeInputReceiver() {}
+
+private:
+    std::unique_ptr<InputChannel> mClientChannel;
+    InputConsumer mConsumer;
+    DynamicInputEventFactory mEventFactory;
+};
+
+class FakeWindowHandle : public WindowInfoHandle {
+public:
+    static const int32_t WIDTH = 600;
+    static const int32_t HEIGHT = 800;
+
+    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+                     InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+          : mName(name) {
+        Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name);
+        mInfo.token = (*channel)->getConnectionToken();
+        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+
+        inputApplicationHandle->updateInfo();
+        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
+
+        mInfo.id = sId++;
+        mInfo.name = name;
+        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
+        mInfo.alpha = 1.0;
+        mInfo.frame.left = 0;
+        mInfo.frame.top = 0;
+        mInfo.frame.right = WIDTH;
+        mInfo.frame.bottom = HEIGHT;
+        mInfo.transform.set(0, 0);
+        mInfo.globalScaleFactor = 1.0;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
+        mInfo.ownerPid = WINDOW_PID;
+        mInfo.ownerUid = WINDOW_UID;
+        mInfo.displayId = displayId;
+        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
+    }
+
+    sp<FakeWindowHandle> clone(int32_t displayId) {
+        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+        handle->mInfo = mInfo;
+        handle->mInfo.displayId = displayId;
+        handle->mInfo.id = sId++;
+        handle->mInputReceiver = mInputReceiver;
+        return handle;
+    }
+
+    void setTouchable(bool touchable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
+    }
+
+    void setFocusable(bool focusable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+
+    void setVisible(bool visible) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    }
+
+    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+        mInfo.dispatchingTimeout = timeout;
+    }
+
+    void setPaused(bool paused) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
+    }
+
+    void setPreventSplitting(bool preventSplitting) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
+    }
+
+    void setSlippery(bool slippery) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
+    }
+
+    void setWatchOutsideTouch(bool watchOutside) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+    }
+
+    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
+
+    void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
+    }
+
+    void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
+
+    void setDisableUserActivity(bool disableUserActivity) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
+    }
+
+    void setAlpha(float alpha) { mInfo.alpha = alpha; }
+
+    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
+
+    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
+    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
+        mInfo.frame.left = frame.left;
+        mInfo.frame.top = frame.top;
+        mInfo.frame.right = frame.right;
+        mInfo.frame.bottom = frame.bottom;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(frame);
+
+        const Rect logicalDisplayFrame = displayTransform.transform(frame);
+        ui::Transform translate;
+        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+        mInfo.transform = translate * displayTransform;
+    }
+
+    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
+    void setIsWallpaper(bool isWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
+    }
+
+    void setDupTouchToWallpaper(bool hasWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+    }
+
+    void setTrustedOverlay(bool trustedOverlay) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+    }
+
+    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
+        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
+    }
+
+    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
+
+    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
+
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) {
+        if (mInputReceiver == nullptr) {
+            return nullptr;
+        }
+        return mInputReceiver->consumeEvent(timeout);
+    }
+
+    void consumeMotion() {
+        std::unique_ptr<InputEvent> event = consume(100ms);
+
+        if (event == nullptr) {
+            LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one.";
+            return;
+        }
+
+        if (event->getType() != InputEventType::MOTION) {
+            LOG(FATAL) << mName << " expected a MotionEvent, got " << *event;
+            return;
+        }
+    }
+
+    sp<IBinder> getToken() { return mInfo.token; }
+
+    const std::string& getName() { return mName; }
+
+    void setOwnerInfo(Pid ownerPid, Uid ownerUid) {
+        mInfo.ownerPid = ownerPid;
+        mInfo.ownerUid = ownerUid;
+    }
+
+    Pid getPid() const { return mInfo.ownerPid; }
+
+    void destroyReceiver() { mInputReceiver = nullptr; }
+
+private:
+    FakeWindowHandle(std::string name) : mName(name){};
+    const std::string mName;
+    std::shared_ptr<FakeInputReceiver> mInputReceiver;
+    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+    friend class sp<FakeWindowHandle>;
+};
+
+std::atomic<int32_t> FakeWindowHandle::sId{1};
+
+} // namespace inputdispatcher
+
+} // namespace android
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index d7dc800..1630769 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -16,7 +16,8 @@
 
 #include <memory>
 
-#include <EventHub.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gestures/GestureConverter.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
@@ -34,9 +35,22 @@
 
 namespace android {
 
-using testing::AllOf;
+namespace input_flags = com::android::input::flags;
 
-class GestureConverterTest : public testing::Test {
+namespace {
+
+const auto TOUCHPAD_PALM_REJECTION =
+        ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection);
+const auto TOUCHPAD_PALM_REJECTION_V2 =
+        ACONFIG_FLAG(input_flags, enable_v2_touchpad_typing_palm_rejection);
+
+} // namespace
+
+using testing::AllOf;
+using testing::ElementsAre;
+using testing::VariantWith;
+
+class GestureConverterTestBase : public testing::Test {
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
@@ -83,22 +97,50 @@
     std::shared_ptr<FakePointerController> mFakePointerController;
 };
 
+class GestureConverterTest : public GestureConverterTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        GestureConverterTestBase::SetUp();
+    }
+};
+
 TEST_F(GestureConverterTest, Move) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithRelativeMotion(-5, 10),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+
+    // The same gesture again should only repeat the HOVER_MOVE and cursor position change, not the
+    // HOVER_ENTER.
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithCoords(POINTER_X - 10, POINTER_Y + 20),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 10, POINTER_Y + 20));
 }
 
 TEST_F(GestureConverterTest, Move_Rotated) {
@@ -108,14 +150,20 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(POINTER_X + 10, POINTER_Y + 5),
+                                          WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
 }
@@ -129,67 +177,87 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                              WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
                            /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
-    ASSERT_EQ(3u, args.size());
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
 
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
+                        /*is_tap=*/false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args.front(),
+                VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                              WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
@@ -201,32 +269,33 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
                         /* is_tap= */ false);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                              WithToolType(ToolType::FINGER),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
@@ -234,24 +303,27 @@
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                       /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
                       /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
-    ASSERT_EQ(3u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0),
+                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
@@ -261,50 +333,59 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(POINTER_X, POINTER_Y - 10),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X, POINTER_Y - 15),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 15),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y - 15),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -315,43 +396,54 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDownTime(downTime),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 10, POINTER_Y),
-                      WithGestureScrollDistance(0, 10, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(POINTER_X - 10, POINTER_Y),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 5, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithCoords(POINTER_X - 15, POINTER_Y),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X - 15, POINTER_Y),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X - 15, POINTER_Y),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
@@ -360,21 +452,22 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionClassification(MotionClassification::NONE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::NONE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
@@ -383,20 +476,21 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
 }
@@ -408,17 +502,19 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
                         /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
     ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
@@ -428,16 +524,17 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like pinch.
     Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
                 AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
@@ -454,7 +551,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -505,7 +603,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -522,30 +620,42 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
@@ -556,7 +666,8 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
@@ -598,7 +709,7 @@
 
     Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -613,23 +724,27 @@
     EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
@@ -639,7 +754,8 @@
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(5u, args.size());
 
     // Four fake fingers should be created. We don't actually care where they are, so long as they
@@ -702,7 +818,7 @@
 
     Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_EQ(1u, args.size());
     arg = std::get<NotifyMotionArgs>(args.front());
     ASSERT_THAT(arg,
@@ -721,38 +837,52 @@
     EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
 
     Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
@@ -762,51 +892,63 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(POINTER_X - 100, POINTER_Y),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(0.8f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(0.8f, EPSILON),
+                              WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
+                              WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
@@ -816,51 +958,63 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON),
-                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(POINTER_X - 100, POINTER_Y),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.2f, EPSILON),
-                      WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
-                      WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.2f, EPSILON),
+                              WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
+                              WithPointerCoords(1, POINTER_X + 120, POINTER_Y),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
@@ -870,21 +1024,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionClassification(MotionClassification::NONE));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
@@ -894,21 +1049,22 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                        GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
 
     // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
     // need to use another gesture type, like scroll.
     Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
                           /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
     ASSERT_FALSE(args.empty());
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
 }
@@ -921,28 +1077,33 @@
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
                         /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
@@ -951,18 +1112,25 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithCoords(POINTER_X, POINTER_Y - 10),
-                      WithGestureScrollDistance(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                      WithToolType(ToolType::FINGER),
-                      WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y - 10),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
@@ -972,31 +1140,40 @@
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringPinch) {
@@ -1006,22 +1183,30 @@
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
 
     std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                      WithMotionClassification(MotionClassification::PINCH),
-                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FlingTapDown) {
@@ -1031,10 +1216,11 @@
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                       WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
                       WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
                       WithDisplayId(ADISPLAY_ID_DEFAULT)));
@@ -1051,52 +1237,57 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
 
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Click) {
@@ -1107,61 +1298,71 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
 
-    ASSERT_EQ(2u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
-
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 }
 
-TEST_F(GestureConverterTest, TapWithTapToClickDisabled) {
+TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
+                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
     // Tap should be ignored when disabled
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
@@ -1169,22 +1370,16 @@
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
                        /* down= */ GESTURES_BUTTON_LEFT,
                        /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
 
     // no events should be generated
     ASSERT_EQ(0u, args.size());
@@ -1193,7 +1388,97 @@
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
-TEST_F(GestureConverterTest, ClickWithTapToClickDisabled) {
+TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+
+    // taps before the threshold should still be ignored
+    currentTime += TAP_ENABLE_DELAY_NANOS.count();
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // taps after the threshold should be recognised
+    currentTime += 1;
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    ASSERT_EQ(6u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                      WithRelativeMotion(0.f, 0.f), WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(1),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithRelativeMotion(0, 0),
+                      WithButtonState(0)));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
     // Click should still produce button press/release events
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
@@ -1203,64 +1488,71 @@
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
 
     Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
-    ASSERT_EQ(2u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
 
-    ASSERT_EQ(3u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    args.pop_front();
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER),
+                                          WithButtonState(0), WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
-TEST_F(GestureConverterTest, MoveEnablesTapToClick) {
+TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
     // initially disable tap-to-click
     mReader->getContext()->setPreventingTouchpadTaps(true);
 
@@ -1269,19 +1561,1533 @@
     converter.setDisplayId(ADISPLAY_ID_DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
-    ASSERT_EQ(1u, args.size());
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    // We don't need to check args here, since it's covered by the Move test.
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
 }
 
+TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    const nsecs_t gestureStartTime = 1000;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Start a move gesture at gestureStartTime
+    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+
+    // Key presses with IME connection should cancel ongoing move gesture
+    nsecs_t currentTime = gestureStartTime + 100;
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
+
+    // any updates in existing move gesture should be ignored
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_EQ(0u, args.size());
+
+    // New gesture should not be affected
+    currentTime += 100;
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+}
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        GestureConverterTestBase::SetUp();
+    }
+};
+
+TEST_F(GestureConverterTestWithChoreographer, Move) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0, 0), WithRelativeMotion(-5, 10),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER.
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(0), WithPressure(0.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                          WithCoords(0, 0), WithRelativeMotion(10, 5),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Press left and right buttons at once
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // Then release the left button
+    Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                          /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // Finally release the right button
+    Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+                           /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
+                        /*is_tap=*/false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args.front(),
+                VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
+                              WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Press the button
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+                        /* is_tap= */ false);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // Move
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // Release the button
+    Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                      /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                      /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(0, -10),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, -15),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER), WithDownTime(downTime),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(-10, 0),
+                                          WithGestureScrollDistance(0, 10, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
+                              WithGestureScrollDistance(0, 5, EPSILON),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(-15, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionClassification(MotionClassification::NONE),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like pinch.
+    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/0);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
+                        /*dy=*/10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
+                         /*dy=*/5);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like pinch.
+    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) {
+    // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+    // start swiping up and then start moving left or right, it'll return gesture events with only Y
+    // deltas until you lift your fingers and start swiping again. That's why each of these tests
+    // only checks movement in one dimension.
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(3),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                         /* dx= */ 10, /* dy= */ 0);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_EQ(5u, args.size());
+
+    // Four fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    PointerCoords finger3Start = arg.pointerCoords[3];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 5, /* dy= */ 0);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(4u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithGestureSwipeFingerCount(4),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(-100, 0), WithPointerCount(1u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(0.8f, EPSILON),
+                              WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithCoords(-100, 0), WithPointerCount(1u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                              WithMotionClassification(MotionClassification::PINCH),
+                              WithGesturePinchScaleFactor(1.1f, EPSILON),
+                              WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::NONE))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
+
+    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+    // need to use another gesture type, like scroll.
+    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
+                          /*dy=*/0);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
+    ASSERT_FALSE(args.empty());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, -10),
+                                          WithGestureScrollDistance(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::TWO_FINGER_SWIPE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+                         GESTURES_ZOOM_START);
+    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithMotionClassification(MotionClassification::PINCH),
+                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0),
+                                          WithMotionClassification(MotionClassification::NONE),
+                                          WithToolType(ToolType::FINGER),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Tap) {
+    // Tap should produce button press/release events
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Click) {
+    // Click should produce button press/release events
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                              /* down= */ GESTURES_BUTTON_LEFT,
+                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* down= */ GESTURES_BUTTON_NONE,
+                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
+                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabledWithDelay,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
+
+    // Tap should be ignored when disabled
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
+                       /* down= */ GESTURES_BUTTON_LEFT,
+                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+
+    // taps before the threshold should still be ignored
+    currentTime += TAP_ENABLE_DELAY_NANOS.count();
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    // no events should be generated
+    ASSERT_EQ(0u, args.size());
+
+    // taps after the threshold should be recognised
+    currentTime += 1;
+    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
+                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
+
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
+
+    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
+                         /* down= */ GESTURES_BUTTON_LEFT,
+                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
+
+    ASSERT_EQ(6u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                      WithRelativeMotion(0.f, 0.f), WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(1),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithRelativeMotion(0.f, 0.f)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithRelativeMotion(0.f, 0.f),
+                      WithButtonState(0)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithRelativeMotion(0, 0),
+                      WithButtonState(0)));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+    // Click should still produce button press/release events
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    // We don't need to check args here, since it's covered by the FlingTapDown test.
+
+    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                              /* down= */ GESTURES_BUTTON_LEFT,
+                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* down= */ GESTURES_BUTTON_NONE,
+                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
+
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+    // initially disable tap-to-click
+    mReader->getContext()->setPreventingTouchpadTaps(true);
+
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    // We don't need to check args here, since it's covered by the Move test.
+
+    // Future taps should be re-enabled
+    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, KeypressCancelsHoverMove,
+                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
+    const nsecs_t gestureStartTime = 1000;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+    // Start a move gesture at gestureStartTime
+    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+
+    // Key presses with IME connection should cancel ongoing move gesture
+    nsecs_t currentTime = gestureStartTime + 100;
+    mFakePolicy->setIsInputMethodConnectionActive(true);
+    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
+
+    // any updates in existing move gesture should be ignored
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
+    ASSERT_EQ(0u, args.size());
+
+    // New gesture should not be affected
+    currentTime += 100;
+    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
+    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
+}
+
 } // namespace android
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 3c87f71..c92736e 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,9 +16,11 @@
 
 #include "../dispatcher/InputDispatcher.h"
 #include "../BlockingQueue.h"
+#include "FakeApplicationHandle.h"
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/silent_death_test.h>
 #include <android-base/stringprintf.h>
@@ -52,6 +54,7 @@
 
 using namespace ftl::flag_operators;
 using testing::AllOf;
+using testing::Not;
 
 namespace {
 
@@ -112,8 +115,6 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr gui::Pid MONITOR_PID{2001};
 
-static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
-
 /**
  * If we expect to receive the event, the timeout can be made very long. When the test are running
  * correctly, we will actually never wait until the end of the timeout because the wait will end
@@ -337,7 +338,7 @@
         std::optional<sp<IBinder>> receivedToken =
                 getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
                                                       mNotifyInputChannelBroken);
-        ASSERT_TRUE(receivedToken.has_value());
+        ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
         ASSERT_EQ(token, *receivedToken);
     }
 
@@ -348,6 +349,8 @@
         mInterceptKeyTimeout = timeout;
     }
 
+    void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; }
+
     void assertUserActivityPoked() {
         std::scoped_lock lock(mLock);
         ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked";
@@ -366,6 +369,30 @@
         ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
     }
 
+    void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
+        std::scoped_lock lock(mLock);
+        mUnhandledKeyHandler = handler;
+    }
+
+    void assertUnhandledKeyReported(int32_t keycode) {
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+        std::optional<int32_t> unhandledKeycode =
+                getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
+                                                      mNotifyUnhandledKey);
+        ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
+        ASSERT_EQ(unhandledKeycode, keycode);
+    }
+
+    void assertUnhandledKeyNotReported() {
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+        std::optional<int32_t> unhandledKeycode =
+                getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
+                                                      mNotifyUnhandledKey);
+        ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
+    }
+
 private:
     std::mutex mLock;
     std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -391,8 +418,14 @@
 
     std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
 
+    std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
+
     BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
 
+    std::condition_variable mNotifyUnhandledKey;
+    std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
+    std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+
     // All three ANR-related callbacks behave the same way, so we use this generic function to wait
     // for a specific container to become non-empty. When the container is non-empty, return the
     // first entry from the container and erase it.
@@ -435,7 +468,6 @@
         condition.wait_for(lock, timeout,
                            [&storage]() REQUIRES(mLock) { return !storage.empty(); });
         if (storage.empty()) {
-            ADD_FAILURE() << "Did not receive the expected callback";
             return std::nullopt;
         }
         T item = storage.front();
@@ -517,7 +549,7 @@
         }
     }
 
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
 
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
         nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
@@ -526,9 +558,12 @@
         return delay;
     }
 
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
                                                  uint32_t) override {
-        return {};
+        std::scoped_lock lock(mLock);
+        mReportedUnhandledKeycodes.emplace(event.getKeyCode());
+        mNotifyUnhandledKey.notify_all();
+        return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
     }
 
     void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
@@ -537,7 +572,8 @@
         /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
          * essentially a passthrough for notifySwitch.
          */
-        mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask);
+        mLastNotifySwitch =
+                NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
     }
 
     void pokeUserActivity(nsecs_t, int32_t, int32_t) override {
@@ -545,6 +581,10 @@
         mPokedUserActivity = true;
     }
 
+    bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override {
+        return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
+    }
+
     void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
         std::scoped_lock lock(mLock);
         mOnPointerDownToken = newToken;
@@ -586,7 +626,8 @@
 
     void SetUp() override {
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, STALE_EVENT_TIMEOUT);
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+
         mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
         // Start InputDispatcher thread
         ASSERT_EQ(OK, mDispatcher->start());
@@ -796,7 +837,8 @@
 }
 
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
-    NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1,
+    NotifySwitchArgs args(InputEvent::nextId(), /*eventTime=*/20, /*policyFlags=*/0,
+                          /*switchValues=*/1,
                           /*switchMask=*/2);
     mDispatcher->notifySwitch(args);
 
@@ -814,37 +856,18 @@
         android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
         android::base::HwTimeoutMultiplier());
 
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
-    FakeApplicationHandle() {
-        mInfo.name = "Fake Application";
-        mInfo.token = sp<BBinder>::make();
-        mInfo.dispatchingTimeoutMillis =
-                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
-    }
-    virtual ~FakeApplicationHandle() {}
-
-    virtual bool updateInfo() override { return true; }
-
-    void setDispatchingTimeout(std::chrono::milliseconds timeout) {
-        mInfo.dispatchingTimeoutMillis = timeout.count();
-    }
-};
-
 class FakeInputReceiver {
 public:
     explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name)
-          : mName(name) {
-        mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel));
-    }
+          : mConsumer(std::move(clientChannel)), mName(name) {}
 
-    InputEvent* consume(std::chrono::milliseconds timeout) {
+    InputEvent* consume(std::chrono::milliseconds timeout, bool handled = false) {
         InputEvent* event;
         std::optional<uint32_t> consumeSeq = receiveEvent(timeout, &event);
         if (!consumeSeq) {
             return nullptr;
         }
-        finishEvent(*consumeSeq);
+        finishEvent(*consumeSeq, handled);
         return event;
     }
 
@@ -860,8 +883,8 @@
         std::chrono::time_point start = std::chrono::steady_clock::now();
         status_t status = WOULD_BLOCK;
         while (status == WOULD_BLOCK) {
-            status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                        &event);
+            status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                       &event);
             std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
             if (elapsed > timeout) {
                 break;
@@ -890,13 +913,13 @@
     /**
      * To be used together with "receiveEvent" to complete the consumption of an event.
      */
-    void finishEvent(uint32_t consumeSeq) {
-        const status_t status = mConsumer->sendFinishedSignal(consumeSeq, true);
+    void finishEvent(uint32_t consumeSeq, bool handled = true) {
+        const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
         ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
     }
 
     void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        const status_t status = mConsumer->sendTimeline(inputEventId, timeline);
+        const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
         ASSERT_EQ(OK, status);
     }
 
@@ -1031,12 +1054,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 = "
@@ -1054,12 +1075,12 @@
                << ": should not have received any events, so consume() should return NULL";
     }
 
-    sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
+    sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
 
-    int getChannelFd() { return mConsumer->getChannel()->getFd().get(); }
+    int getChannelFd() { return mConsumer.getChannel()->getFd().get(); }
 
-protected:
-    std::unique_ptr<InputConsumer> mConsumer;
+private:
+    InputConsumer mConsumer;
     PreallocatedInputEventFactory mEventFactory;
 
     std::string mName;
@@ -1163,6 +1184,11 @@
         mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
     }
 
+    void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH,
+                             shouldGlobalStylusBlockTouch);
+    }
+
     void setAlpha(float alpha) { mInfo.alpha = alpha; }
 
     void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
@@ -1202,31 +1228,27 @@
 
     void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
 
-    KeyEvent* consumeKey() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
+    const KeyEvent& consumeKey(bool handled = true) {
+        const InputEvent& event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
+        if (event.getType() != InputEventType::KEY) {
+            LOG(FATAL) << "Instead of key event, got " << event;
         }
-        if (event->getType() != InputEventType::KEY) {
-            ADD_FAILURE() << "Instead of key event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<KeyEvent*>(event);
+        return static_cast<const KeyEvent&>(event);
     }
 
     void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
-        KeyEvent* keyEvent = consumeKey();
-        ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher;
-        ASSERT_THAT(*keyEvent, matcher);
+        const KeyEvent& keyEvent = consumeKey();
+        ASSERT_THAT(keyEvent, matcher);
     }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags);
+        consumeKeyEvent(AllOf(WithKeyAction(ACTION_DOWN), WithDisplayId(expectedDisplayId),
+                              WithFlags(expectedFlags)));
     }
 
     void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
+        consumeKeyEvent(AllOf(WithKeyAction(ACTION_UP), WithDisplayId(expectedDisplayId),
+                              WithFlags(expectedFlags)));
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
@@ -1248,44 +1270,46 @@
 
     void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
                               std::optional<int32_t> expectedFlags = std::nullopt) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(
+                AllOf(WithMotionAction(ACTION_DOWN),
+                      testing::Conditional(expectedDisplayId.has_value(),
+                                           WithDisplayId(*expectedDisplayId), testing::_),
+                      testing::Conditional(expectedFlags.has_value(), WithFlags(*expectedFlags),
+                                           testing::_)));
     }
 
     void consumeMotionPointerDown(int32_t pointerIdx,
                                   int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                   int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                 int32_t expectedFlags = 0) {
-        int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                          int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDisplayId(expectedDisplayId),
+                                 WithFlags(expectedFlags)));
     }
 
     void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                               int32_t expectedFlags = 0) {
-        consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId,
-                     expectedFlags);
+        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
     }
 
-    void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                              int32_t expectedFlags = 0) {
-        MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent);
-        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX());
-        EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY());
+    void consumeMotionOutsideWithZeroedCoords() {
+        consumeMotionEvent(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithRawCoords(0, 0)));
     }
 
     void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
@@ -1300,18 +1324,15 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
-    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        MotionEvent* motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
-        ASSERT_THAT(*motionEvent, matcher);
-    }
-
-    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
-                      std::optional<int32_t> expectedDisplayId,
-                      std::optional<int32_t> expectedFlags) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
-        mInputReceiver->consumeEvent(expectedEventType, expectedAction, expectedDisplayId,
-                                     expectedFlags);
+    const MotionEvent& consumeMotionEvent(
+            const ::testing::Matcher<MotionEvent>& matcher = testing::_) {
+        const InputEvent& event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        if (event.getType() != InputEventType::MOTION) {
+            LOG(FATAL) << "Instead of motion event, got " << event;
+        }
+        const auto& motionEvent = static_cast<const MotionEvent&>(event);
+        EXPECT_THAT(motionEvent, matcher);
+        return motionEvent;
     }
 
     void consumeDragEvent(bool isExiting, float x, float y) {
@@ -1342,26 +1363,6 @@
         mInputReceiver->sendTimeline(inputEventId, timeline);
     }
 
-    InputEvent* consume(std::chrono::milliseconds timeout) {
-        if (mInputReceiver == nullptr) {
-            return nullptr;
-        }
-        return mInputReceiver->consume(timeout);
-    }
-
-    MotionEvent* consumeMotion() {
-        InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed : no event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << "Instead of motion event, got " << *event;
-            return nullptr;
-        }
-        return static_cast<MotionEvent*>(event);
-    }
-
     void assertNoEvents() {
         if (mInputReceiver == nullptr &&
             mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
@@ -1393,48 +1394,56 @@
     std::shared_ptr<FakeInputReceiver> mInputReceiver;
     static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
     friend class sp<FakeWindowHandle>;
+
+    const InputEvent& consume(std::chrono::milliseconds timeout, bool handled = true) {
+        if (mInputReceiver == nullptr) {
+            LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
+        }
+        InputEvent* event = mInputReceiver->consume(timeout, handled);
+        if (event == nullptr) {
+            LOG(FATAL) << "Consume failed: no event";
+        }
+        return *event;
+    }
 };
 
 std::atomic<int32_t> FakeWindowHandle::sId{1};
 
 class FakeMonitorReceiver {
 public:
-    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId) {
-        base::Result<std::unique_ptr<InputChannel>> channel =
-                dispatcher.createInputMonitor(displayId, name, MONITOR_PID);
-        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-    }
+    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+          : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {}
 
-    sp<IBinder> getToken() { return mInputReceiver->getToken(); }
+    sp<IBinder> getToken() { return mInputReceiver.getToken(); }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
-                                     expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
+                                    expectedFlags);
     }
 
     std::optional<int32_t> receiveEvent() {
-        return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        return mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
     }
 
-    void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
+    void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); }
 
     void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeMotionEvent(
+        mInputReceiver.consumeMotionEvent(
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                       WithDisplayId(expectedDisplayId),
                       WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
@@ -1443,20 +1452,20 @@
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
-                                     /*expectedFlags=*/0);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+                                    /*expectedFlags=*/0);
     }
 
     void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        mInputReceiver->consumeMotionEvent(matcher);
+        mInputReceiver.consumeMotionEvent(matcher);
     }
 
-    MotionEvent* consumeMotion() { return mInputReceiver->consumeMotion(); }
+    MotionEvent* consumeMotion() { return mInputReceiver.consumeMotion(); }
 
-    void assertNoEvents() { mInputReceiver->assertNoEvents(); }
+    void assertNoEvents() { mInputReceiver.assertNoEvents(); }
 
 private:
-    std::unique_ptr<FakeInputReceiver> mInputReceiver;
+    FakeInputReceiver mInputReceiver;
 };
 
 static InputEventInjectionResult injectKey(
@@ -1560,9 +1569,9 @@
 static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, POLICY_FLAG_PASS_TO_USER, action,
+                       /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1571,9 +1580,9 @@
                                                 int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, AMETA_META_ON,
-                       currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C,
+                       AMETA_META_ON, currentTime);
 
     return args;
 }
@@ -1582,9 +1591,9 @@
                                               int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
-                       displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, KEY_ASSISTANT,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
+                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST,
+                       KEY_ASSISTANT, AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1612,9 +1621,9 @@
 
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
-    NotifyMotionArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, source, displayId,
-                          POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0,
-                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
+    NotifyMotionArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, source,
+                          displayId, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
@@ -1633,7 +1642,8 @@
 
 static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
         const PointerCaptureRequest& request) {
-    return NotifyPointerCaptureChangedArgs(/*id=*/0, systemTime(SYSTEM_TIME_MONOTONIC), request);
+    return NotifyPointerCaptureChangedArgs(InputEvent::nextId(), systemTime(SYSTEM_TIME_MONOTONIC),
+                                           request);
 }
 
 } // namespace
@@ -1763,8 +1773,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
@@ -1772,11 +1784,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
@@ -2160,6 +2174,69 @@
 }
 
 /**
+ * Same as the above 'TwoPointerCancelInconsistentPolicy' test, but for hovers.
+ * The policy typically sets POLICY_FLAG_PASS_TO_USER to the events. But when the display is not
+ * interactive, it might stop sending this flag.
+ * We've already ensured the consistency of the touch event in this case, and we should also ensure
+ * the consistency of the hover event in this case.
+ *
+ * Test procedure:
+ * HOVER_ENTER -> HOVER_MOVE -> (stop sending POLICY_FLAG_PASS_TO_USER) -> HOVER_EXIT
+ * HOVER_ENTER -> HOVER_MOVE -> HOVER_EXIT
+ *
+ * We expect to receive two full streams of hover events.
+ */
+TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) {
+    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, 300, 300));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+    // Send hover exit without the default policy flags.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(0)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // Send a simple hover event stream, ensure dispatcher not crashed and window can receive
+    // right event.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(200).y(201))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(201).y(202))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .policyFlags(DEFAULT_POLICY_FLAGS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(201).y(202))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is hovered from the right window into the left window.
  * Next, we tap on the left window, where the cursor was last seen.
@@ -2514,9 +2591,9 @@
 
 /**
  * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
- * touch is not dropped, because stylus hover should be ignored.
+ * touch is dropped, because stylus hover takes precedence.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2545,34 +2622,29 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
 
-    // Stylus hover is canceled because touch is down
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
-                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
-                                     WithCoords(140, 145)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(141, 146)));
+    // Touch is ignored because stylus is hovering
 
-    // Subsequent stylus movements are ignored
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
 
-    // but subsequent touches continue to be delivered
+    // and subsequent touches continue to be ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                       .build());
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(142, 147)));
+    window->assertNoEvents();
 }
 
 /**
  * One window. Touch down on the window. Then, stylus hover on the window from another device.
- * Ensure that touch is not canceled, because stylus hover should be dropped.
+ * Ensure that touch is canceled, because stylus hover should take precedence.
  */
-TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2604,15 +2676,21 @@
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
-    // Stylus hover movement is dropped
+    // Stylus hover movement causes touch to be canceled
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
+                                     WithCoords(141, 146)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
 
+    // Subsequent touch movements are ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                       .build());
-    // Subsequent touch movements are delivered correctly
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(142, 147)));
+
+    window->assertNoEvents();
 }
 
 /**
@@ -2929,11 +3007,11 @@
  * Three windows: a window on the left, a window on the right, and a spy window positioned above
  * both.
  * Check hover in left window and touch down in the right window.
- * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
+ * At first, spy should receive hover. Spy shouldn't receive touch while stylus is hovering.
  * At the same time, left and right should be getting independent streams of hovering and touch,
  * respectively.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindow =
@@ -2973,28 +3051,25 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
     leftWindow->assertNoEvents();
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->assertNoEvents();
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue. They should be delivered to the left window only.
+    // Stylus movements continue. They should be delivered to the left window and the spy.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
-    // Touch movements continue. They should be delivered to the right window and to the spy
+    // Touch movements continue. They should be delivered to the right window only
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
                                       .build());
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
 
@@ -3209,7 +3284,7 @@
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3235,10 +3310,7 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    // The touch device should cause hover to stop!
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    // The touch device should be ignored!
 
     // Continue hovering with stylus.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3248,7 +3320,9 @@
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                         .build()));
-    // Hovers are now ignored
+    // Hovers continue to work
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     // Lift up the finger
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3258,7 +3332,6 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -3267,12 +3340,156 @@
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
                                         .build()));
-    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                     WithDeviceId(stylusDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
     window->assertNoEvents();
 }
 
 /**
+ * If stylus is down anywhere on the screen, then touches should not be delivered to windows that
+ * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus down on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is down on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusDownBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus down in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is down somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
+ * If stylus is hovering anywhere on the screen, then touches should not be delivered to windows
+ * that have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
+ *
+ * Two windows: one on the left and one on the right.
+ * The window on the right has GLOBAL_STYLUS_BLOCKS_TOUCH config.
+ * Stylus hover on the left window, and then touch down on the right window.
+ * Check that the right window doesn't get touches while the stylus is hovering on the left window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> sbtRightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher,
+                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+    sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
+    sbtRightWindow->setGlobalStylusBlocksTouch(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *sbtRightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+
+    // Stylus hover in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(52))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Finger tap on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(151))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+
+    // The touch should be blocked, because stylus is hovering somewhere else on screen!
+    sbtRightWindow->assertNoEvents();
+
+    // Continue stylus motion, and ensure it's not impacted.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(53))
+                                      .deviceId(stylusDeviceId)
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+
+    // Now that the stylus gesture is done, touches should be getting delivered correctly.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(153))
+                                      .deviceId(touchDeviceId)
+                                      .build());
+    sbtRightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * A spy window above a window with no input channel.
  * Start hovering with a stylus device, and then tap with it.
  * Ensure spy window receives the entire sequence.
@@ -3331,6 +3548,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.
@@ -3505,20 +3760,18 @@
 
     mDispatcher->waitForIdle();
 
-    MotionEvent* motionEvent1 = window1->consumeMotion();
-    ASSERT_NE(motionEvent1, nullptr);
+    const MotionEvent& motionEvent1 = window1->consumeMotionEvent();
     window2->assertNoEvents();
-    nsecs_t downTimeForWindow1 = motionEvent1->getDownTime();
-    ASSERT_EQ(motionEvent1->getDownTime(), motionEvent1->getEventTime());
+    nsecs_t downTimeForWindow1 = motionEvent1.getDownTime();
+    ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime());
 
     // Now touch down on the window with another pointer
     mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}));
     mDispatcher->waitForIdle();
-    MotionEvent* motionEvent2 = window2->consumeMotion();
-    ASSERT_NE(motionEvent2, nullptr);
-    nsecs_t downTimeForWindow2 = motionEvent2->getDownTime();
+    const MotionEvent& motionEvent2 = window2->consumeMotionEvent();
+    nsecs_t downTimeForWindow2 = motionEvent2.getDownTime();
     ASSERT_NE(downTimeForWindow1, downTimeForWindow2);
-    ASSERT_EQ(motionEvent2->getDownTime(), motionEvent2->getEventTime());
+    ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime());
 
     // Now move the pointer on the second window
     mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}));
@@ -4113,8 +4366,7 @@
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
-                         AKEY_EVENT_FLAG_CANCELED);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED);
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
@@ -4238,6 +4490,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();
 }
 
 /**
@@ -4347,12 +4605,12 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    const MotionEvent* event = window->consumeMotion();
-    EXPECT_EQ(POINTER_1_DOWN, event->getAction());
-    EXPECT_EQ(70, event->getX(0));  // 50 + 20
-    EXPECT_EQ(90, event->getY(0));  // 50 + 40
-    EXPECT_EQ(-10, event->getX(1)); // -30 + 20
-    EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+    const MotionEvent& event = window->consumeMotionEvent();
+    EXPECT_EQ(POINTER_1_DOWN, event.getAction());
+    EXPECT_EQ(70, event.getX(0));  // 50 + 20
+    EXPECT_EQ(90, event.getY(0));  // 50 + 40
+    EXPECT_EQ(-10, event.getX(1)); // -30 + 20
+    EXPECT_EQ(-10, event.getY(1)); // -50 + 40
 }
 
 /**
@@ -4618,15 +4876,15 @@
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken()));
 
     // windowDefaultDisplay gets cancel
-    MotionEvent* event = windowDefaultDisplay->consumeMotion();
-    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
+    const MotionEvent& event = windowDefaultDisplay->consumeMotionEvent();
+    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event.getAction());
 
     // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
     // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
     // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
     // SECOND_DISPLAY_ID, the x and y coordinates are 200
-    EXPECT_EQ(100, event->getX(0));
-    EXPECT_EQ(100, event->getY(0));
+    EXPECT_EQ(100, event.getX(0));
+    EXPECT_EQ(100, event.getY(0));
 }
 
 /**
@@ -4756,19 +5014,18 @@
                                                  {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
-    const MotionEvent* event = secondWindow->consumeMotion();
-    ASSERT_NE(nullptr, event);
-    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
+    const MotionEvent& event = secondWindow->consumeMotionEvent();
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event.getAction());
 
     // Ensure that the events from the "getRaw" API are in logical display coordinates.
-    EXPECT_EQ(300, event->getRawX(0));
-    EXPECT_EQ(880, event->getRawY(0));
+    EXPECT_EQ(300, event.getRawX(0));
+    EXPECT_EQ(880, event.getRawY(0));
 
     // Ensure that the x and y values are in the window's coordinate space.
     // The left-top of the second window is at (100, 200) in display space, which is (200, 800) in
     // the logical display space. This will be the origin of the window space.
-    EXPECT_EQ(100, event->getX(0));
-    EXPECT_EQ(80, event->getY(0));
+    EXPECT_EQ(100, event.getX(0));
+    EXPECT_EQ(80, event.getY(0));
 }
 
 TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) {
@@ -5842,8 +6099,7 @@
                                              motionArgs.pointerCoords[0].getX() - 10);
 
     mDispatcher->notifyMotion(motionArgs);
-    window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT,
-                         /*expectedFlags=*/0);
+    window->consumeMotionMove(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0);
 }
 
 /**
@@ -5913,10 +6169,9 @@
     const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN);
     mDispatcher->notifyKey(keyArgs);
 
-    KeyEvent* event = window->consumeKey();
-    ASSERT_NE(event, nullptr);
+    const KeyEvent& event = window->consumeKey();
 
-    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
+    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::KEY);
 
@@ -5957,10 +6212,9 @@
                                ADISPLAY_ID_DEFAULT);
     mDispatcher->notifyMotion(motionArgs);
 
-    MotionEvent* event = window->consumeMotion();
-    ASSERT_NE(event, nullptr);
+    const MotionEvent& event = window->consumeMotionEvent();
 
-    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
+    std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(event);
     ASSERT_NE(verified, nullptr);
     ASSERT_EQ(verified->type, VerifiedInputEvent::Type::MOTION);
 
@@ -6464,10 +6718,265 @@
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
 }
 
+TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                                           ADISPLAY_ID_DEFAULT);
+    left->setFrame(Rect(0, 0, 100, 100));
+    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    right->setFrame(Rect(100, 0, 200, 100));
+    sp<FakeWindowHandle> spy =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    spy->setFrame(Rect(0, 0, 200, 100));
+    spy->setTrustedOverlay(true);
+    spy->setSpy(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+    // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId.
+    NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                                     ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+    mDispatcher->notifyMotion(notifyArgs);
+
+    const MotionEvent& leftEnter = left->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)),
+                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                  Not(WithEventId(notifyArgs.id)),
+                                  Not(WithEventId(leftEnter.getId())),
+                                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    // Send move to the right window, and ensure hover exit and enter are synthesized with new ids.
+    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+                                    {PointF{150, 50}});
+    mDispatcher->notifyMotion(notifyArgs);
+
+    const MotionEvent& leftExit = left->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), Not(WithEventId(notifyArgs.id)),
+                  WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    right->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                    Not(WithEventId(notifyArgs.id)),
+                                    Not(WithEventId(leftExit.getId())),
+                                    WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
+}
+
+class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
+protected:
+    std::shared_ptr<FakeApplicationHandle> mApp;
+    sp<FakeWindowHandle> mWindow;
+
+    virtual void SetUp() override {
+        InputDispatcherTest::SetUp();
+
+        mApp = std::make_shared<FakeApplicationHandle>();
+
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+        mWindow->setFrame(Rect(0, 0, 100, 100));
+
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
+        setFocusedWindow(mWindow);
+        ASSERT_NO_FATAL_FAILURE(mWindow->consumeFocusEvent(/*hasFocus=*/true));
+    }
+
+    void setFallback(int32_t keycode) {
+        mFakePolicy->setUnhandledKeyHandler([keycode](const KeyEvent& event) {
+            return KeyEventBuilder(event).keyCode(keycode).build();
+        });
+    }
+
+    void consumeKey(bool handled, const ::testing::Matcher<KeyEvent>& matcher) {
+        const KeyEvent& event = mWindow->consumeKey(handled);
+        ASSERT_THAT(event, matcher);
+    }
+};
+
+TEST_F(InputDispatcherFallbackKeyTest, PolicyNotNotifiedForHandledKey) {
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/true, AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, PolicyNotifiedForUnhandledKey) {
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/false, AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, NoFallbackRequestedByPolicy) {
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+
+    // Since the policy did not request any fallback to be generated, ensure there are no events.
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, FallbackDispatchForUnhandledKey) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+
+    // Since the key was not handled, ensure the fallback event was dispatched instead.
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // Release the original key, and ensure the fallback key is also released.
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+    mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, AppHandlesPreviouslyUnhandledKey) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event, but handle the fallback.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // Release the original key, and ensure the fallback key is also released.
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    // But this time, the app handles the original key.
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    // Ensure the fallback key is canceled.
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+    mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, AppDoesNotHandleFallback) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    // App does not handle the fallback either, so ensure another fallback is not generated.
+    setFallback(AKEYCODE_C);
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // Release the original key, and ensure the fallback key is also released.
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+    mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, InconsistentPolicyCancelsFallback) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event, so fallback is generated.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // Release the original key, but assume the policy is misbehaving and it
+    // generates an inconsistent fallback to the one from the DOWN event.
+    setFallback(AKEYCODE_C);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    // Ensure the fallback key reported before as DOWN is canceled due to the inconsistency.
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+    mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, CanceledKeyCancelsFallback) {
+    setFallback(AKEYCODE_B);
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+    // Do not handle this key event, so fallback is generated.
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+    // The original key is canceled.
+    mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                                   .keyCode(AKEYCODE_A)
+                                   .addFlag(AKEY_EVENT_FLAG_CANCELED)
+                                   .build());
+    consumeKey(/*handled=*/false,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A),
+                     WithFlags(AKEY_EVENT_FLAG_CANCELED)));
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+    // Ensure the fallback key is also canceled due to the original key being canceled.
+    consumeKey(/*handled=*/true,
+               AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+                     WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+    mWindow->assertNoEvents();
+}
+
 class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
 protected:
-    static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
-    static constexpr nsecs_t KEY_REPEAT_DELAY = 40 * 1000000;   // 40 ms
+    static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
+    static constexpr std::chrono::nanoseconds KEY_REPEAT_DELAY = 40ms;
 
     std::shared_ptr<FakeApplicationHandle> mApp;
     sp<FakeWindowHandle> mWindow;
@@ -6515,7 +7024,7 @@
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+        mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
                               /*expectedFlags=*/0);
     }
 };
@@ -6585,10 +7094,9 @@
     GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
+        const KeyEvent& repeatEvent = mWindow->consumeKey();
         EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER,
-                  IdGenerator::getSource(repeatEvent->getId()));
+                  IdGenerator::getSource(repeatEvent.getId()));
     }
 }
 
@@ -6598,9 +7106,8 @@
 
     std::unordered_set<int32_t> idSet;
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
-        KeyEvent* repeatEvent = mWindow->consumeKey();
-        ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
-        int32_t id = repeatEvent->getId();
+        const KeyEvent& repeatEvent = mWindow->consumeKey();
+        int32_t id = repeatEvent.getId();
         EXPECT_EQ(idSet.end(), idSet.find(id));
         idSet.insert(id);
     }
@@ -6689,8 +7196,7 @@
     mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
     // Old focus should receive a cancel event.
-    windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
-                                    AKEY_EVENT_FLAG_CANCELED);
+    windowInSecondary->consumeKeyUp(ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
     ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
@@ -7168,24 +7674,21 @@
     void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction,
                             const std::vector<PointF>& points) {
         const std::string name = window->getName();
-        MotionEvent* motionEvent = window->consumeMotion();
+        const MotionEvent& motionEvent =
+                window->consumeMotionEvent(WithMotionAction(expectedAction));
 
-        ASSERT_NE(nullptr, motionEvent)
-                << name.c_str() << ": consumer should have returned non-NULL event.";
-
-        ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
-        ASSERT_EQ(points.size(), motionEvent->getPointerCount());
+        ASSERT_EQ(points.size(), motionEvent.getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
             float expectedY = points[i].y;
 
-            EXPECT_EQ(expectedX, motionEvent->getX(i))
+            EXPECT_EQ(expectedX, motionEvent.getX(i))
                     << "expected " << expectedX << " for x[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent->getX(i);
-            EXPECT_EQ(expectedY, motionEvent->getY(i))
+                    << ", got " << motionEvent.getX(i);
+            EXPECT_EQ(expectedY, motionEvent.getY(i))
                     << "expected " << expectedY << " for y[" << i << "] coord of " << name.c_str()
-                    << ", got " << motionEvent->getY(i);
+                    << ", got " << motionEvent.getY(i);
         }
     }
 
@@ -7404,12 +7907,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() {
@@ -7529,6 +8035,8 @@
     mWindow->consumeFocusEvent(false);
 
     KeyEvent event;
+    static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
+    mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
     const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC) -
             std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count();
 
@@ -7844,10 +8352,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));
+    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.
@@ -7856,12 +8364,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();
 }
 
@@ -7993,8 +8501,7 @@
                                         .build()));
     mFocusedWindow->consumeMotionDown();
     mFocusedWindow->consumeMotionUp();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -8070,8 +8577,7 @@
 // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
     std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(downEventSequenceNum);
@@ -8203,8 +8709,7 @@
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                                  {FOCUSED_WINDOW_LOCATION}));
-    mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
-                                   ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
 
     // Touch Window 2
     mDispatcher->notifyMotion(
@@ -8304,6 +8809,104 @@
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyAnrWasNotCalled());
 }
 
+/**
+ * If we are pruning input queue, we should never drop pointer events. Otherwise, we risk having
+ * an inconsistent event stream inside the dispatcher. In this test, we make sure that the
+ * dispatcher doesn't prune pointer events incorrectly.
+ *
+ * This test reproduces a crash in InputDispatcher.
+ * To reproduce the crash, we need to simulate the conditions for "pruning input queue" to occur.
+ *
+ * Keep the currently focused application (mApplication), and have no focused window.
+ * We set up two additional windows:
+ * 1) The navigation bar window. This simulates the system "NavigationBar", which is used in the
+ * 3-button navigation mode. This window injects a BACK button when it's touched. 2) The application
+ * window. This window is not focusable, but is touchable.
+ *
+ * We first touch the navigation bar, which causes it to inject a key. Since there's no focused
+ * window, the dispatcher doesn't process this key, and all other events inside dispatcher are now
+ * blocked. The dispatcher is waiting for 'mApplication' to add a focused window.
+ *
+ * Now, we touch "Another window". This window is owned by a different application than
+ * 'mApplication'. This causes the dispatcher to stop waiting for 'mApplication' to add a focused
+ * window. Now, the "pruning input queue" behaviour should kick in, and the dispatcher should start
+ * dropping the events from its queue. Ensure that no crash occurs.
+ *
+ * In this test, we are setting long timeouts to prevent ANRs and events dropped due to being stale.
+ * This does not affect the test running time.
+ */
+TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvents) {
+    std::shared_ptr<FakeApplicationHandle> systemUiApplication =
+            std::make_shared<FakeApplicationHandle>();
+    systemUiApplication->setDispatchingTimeout(3000ms);
+    mFakePolicy->setStaleEventTimeout(3000ms);
+    sp<FakeWindowHandle> navigationBar =
+            sp<FakeWindowHandle>::make(systemUiApplication, mDispatcher, "NavigationBar",
+                                       ADISPLAY_ID_DEFAULT);
+    navigationBar->setFocusable(false);
+    navigationBar->setWatchOutsideTouch(true);
+    navigationBar->setFrame(Rect(0, 0, 100, 100));
+
+    mApplication->setDispatchingTimeout(3000ms);
+    // 'mApplication' is already focused, but we call it again here to make it explicit.
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> appWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Another window",
+                                       ADISPLAY_ID_DEFAULT);
+    appWindow->setFocusable(false);
+    appWindow->setFrame(Rect(100, 100, 200, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*navigationBar->getInfo(), *appWindow->getInfo()}, {}, 0, 0});
+    // 'mFocusedWindow' is no longer in the dispatcher window list, and therefore loses focus
+    mFocusedWindow->consumeFocusEvent(false);
+
+    // Touch down the navigation bar. It consumes the touch and injects a key into the dispatcher
+    // in response.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Key will not be sent anywhere because we have no focused window. It will remain pending.
+    // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is.
+    InputEventInjectionResult result =
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+                      /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
+
+    // Finish the gesture - lift up finger and inject ACTION_UP key event
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+                       InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+                       /*allowKeyRepeat=*/false);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
+    // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be
+    // getting any events yet.
+    navigationBar->assertNoEvents();
+
+    // Now touch "Another window". This touch is going to a different application than the one we
+    // are waiting for (which is 'mApplication').
+    // This should cause the dispatcher to drop the pending focus-dispatched events (like the key
+    // trying to be injected) and to continue processing the rest of the events in the original
+    // order.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+                                      .build());
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    navigationBar->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+    appWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    appWindow->assertNoEvents();
+    navigationBar->assertNoEvents();
+}
+
 // These tests ensure we cannot send touch events to a window that's positioned behind a window
 // that has feature NO_INPUT_CHANNEL.
 // Layout:
@@ -9317,6 +9920,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(AllOf(WithMotionAction(ACTION_DOWN), WithPointerIds({1})));
+    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(ACTION_MOVE, 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, firstFingerMoveEvent, 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);
 
@@ -9499,8 +10146,7 @@
                                         .displayId(SECOND_DISPLAY_ID)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                    SECOND_DISPLAY_ID, /*expectedFlag=*/0);
+    windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID, /*expectedFlag=*/0);
     // Update window again.
     mDispatcher->onWindowInfosChanged(
             {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
@@ -9654,6 +10300,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) {
@@ -9675,6 +10334,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
@@ -10552,6 +11212,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() {
@@ -10828,4 +11507,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/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index dac4ea0..5f43bd2 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -19,29 +19,42 @@
 #include <InputReaderBase.h>
 #include <gtest/gtest.h>
 #include <ui/Rotation.h>
+#include <utils/Timers.h>
 
 namespace android {
 
 using testing::Return;
 
-void InputMapperUnitTest::SetUp() {
+void InputMapperUnitTest::SetUpWithBus(int bus) {
     mFakePointerController = std::make_shared<FakePointerController>();
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
+    mFakePolicy = sp<FakeInputReaderPolicy>::make();
 
     EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
             .WillRepeatedly(Return(mFakePointerController));
 
-    EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
-    InputDeviceIdentifier identifier;
-    identifier.name = "device";
-    identifier.location = "USB1";
-    identifier.bus = 0;
+    EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
 
-    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier));
+    EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
+
+    mIdentifier.name = "device";
+    mIdentifier.location = "USB1";
+    mIdentifier.bus = bus;
+    EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID))
+            .WillRepeatedly(Return(mIdentifier));
+    EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) {
+        return mPropertyMap;
+    });
+}
+
+void InputMapperUnitTest::createDevice() {
     mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
-                                            /*generation=*/2, identifier);
+                                            /*generation=*/2, mIdentifier);
+    mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
     mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+    std::list<NotifyArgs> _ =
+            mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{});
 }
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
@@ -80,9 +93,15 @@
 }
 
 std::list<NotifyArgs> InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) {
+    nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+    return process(when, type, code, value);
+}
+
+std::list<NotifyArgs> InputMapperUnitTest::process(nsecs_t when, int32_t type, int32_t code,
+                                                   int32_t value) {
     RawEvent event;
-    event.when = systemTime(SYSTEM_TIME_MONOTONIC);
-    event.readTime = event.when;
+    event.when = when;
+    event.readTime = when;
     event.deviceId = mMapper->getDeviceContext().getEventHubId();
     event.type = type;
     event.code = code;
@@ -206,8 +225,8 @@
     return generatedArgs;
 }
 
-void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                        float min, float max, float flat, float fuzz) {
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz) {
     const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source);
     ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source;
     ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source;
@@ -218,11 +237,9 @@
     ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
 }
 
-void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y,
-                                          float pressure, float size, float touchMajor,
-                                          float touchMinor, float toolMajor, float toolMinor,
-                                          float orientation, float distance,
-                                          float scaledAxisEpsilon) {
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon) {
     ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon);
     ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon);
     ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON);
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index c2ac258..e176a65 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -32,6 +32,7 @@
 #include "InterfaceMocks.h"
 #include "TestConstants.h"
 #include "TestInputListener.h"
+#include "input/PropertyMap.h"
 
 namespace android {
 
@@ -41,7 +42,15 @@
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr float INITIAL_CURSOR_X = 400;
     static constexpr float INITIAL_CURSOR_Y = 240;
-    virtual void SetUp() override;
+    virtual void SetUp() override { SetUpWithBus(0); }
+    virtual void SetUpWithBus(int bus);
+
+    /**
+     * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of
+     * mPropertyMap, so tests that need to set configuration properties should do so before calling
+     * this. Others will most likely want to call it in their SetUp method.
+     */
+    void createDevice();
 
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
 
@@ -52,8 +61,11 @@
     void setKeyCodeState(KeyState state, std::set<int> keyCodes);
 
     std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
+    std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
 
+    InputDeviceIdentifier mIdentifier;
     MockEventHubInterface mMockEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
     std::shared_ptr<FakePointerController> mFakePointerController;
     MockInputReaderContext mMockInputReaderContext;
     std::unique_ptr<InputDevice> mDevice;
@@ -62,6 +74,7 @@
     InputReaderConfiguration mReaderConfiguration;
     // The mapper should be created by the subclasses.
     std::unique_ptr<InputMapper> mMapper;
+    PropertyMap mPropertyMap;
 };
 
 /**
@@ -128,13 +141,13 @@
     void resetMapper(InputMapper& mapper, nsecs_t when);
 
     std::list<NotifyArgs> handleTimeout(InputMapper& mapper, nsecs_t when);
-
-    static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source,
-                                  float min, float max, float flat, float fuzz);
-    static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure,
-                                    float size, float touchMajor, float touchMinor, float toolMajor,
-                                    float toolMinor, float orientation, float distance,
-                                    float scaledAxisEpsilon = 1.f);
 };
 
+void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min,
+                       float max, float flat, float fuzz);
+
+void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, float size,
+                         float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+                         float orientation, float distance, float scaledAxisEpsilon = 1.f);
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 64ae9e8..91aa0ca 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -37,6 +37,7 @@
 #include <UinputDevice.h>
 #include <VibratorInputMapper.h>
 #include <android-base/thread_annotations.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
@@ -96,8 +97,8 @@
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
 static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
-// Maximum smoothing time delta so that we don't generate events too far into the future.
-constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
+
+namespace input_flags = com::android::input::flags;
 
 template<typename T>
 static inline T min(T a, T b) {
@@ -229,6 +230,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);
@@ -245,6 +251,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);
     }
@@ -2869,6 +2880,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{.when = ARBITRARY_TIME,
+                   .readTime = ARBITRARY_TIME,
+                   .deviceId = EVENTHUB_ID,
+                   .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 {
@@ -4097,9 +4162,9 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-// --- CursorInputMapperTest ---
+// --- CursorInputMapperTestBase ---
 
-class CursorInputMapperTest : public InputMapperTest {
+class CursorInputMapperTestBase : public InputMapperTest {
 protected:
     static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
 
@@ -4133,11 +4198,11 @@
     }
 };
 
-const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6;
 
-void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
-                                               int32_t originalY, int32_t rotatedX,
-                                               int32_t rotatedY) {
+void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
+                                                   int32_t originalY, int32_t rotatedX,
+                                                   int32_t rotatedY) {
     NotifyMotionArgs args;
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX);
@@ -4151,253 +4216,15 @@
                                       float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
 }
 
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+// --- CursorInputMapperTest ---
 
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    // Initially there may not be a valid motion range.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the bounds are set, then there should be a valid motion range.
-    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
-
-    InputDeviceInfo info2;
-    mapper.populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
-            1, 800 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE,
-            2, 480 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    InputDeviceInfo info;
-    mapper.populateDeviceInfo(info);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
-            -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
-            AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL,
-            0.0f, 1.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    // Button release.  Should have same down time.
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
-    ASSERT_EQ(DEVICE_ID, args.deviceId);
-    ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_EQ(0, args.flags);
-    ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
-    ASSERT_EQ(0, args.buttonState);
-    ASSERT_EQ(0, args.edgeFlags);
-    ASSERT_EQ(uint32_t(1), args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
-    ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
-    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Motion in X but not Y.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f,
-                                                      0.0f));
-
-    // Motion in Y but not X.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Button press.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f));
-
-    // Button release.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
-
-TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
-    addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyMotionArgs args;
-
-    // Combined X, Y and Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Move X, Y a bit while pressed.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0],
-                                                      2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                      1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f));
-
-    // Release Button.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f));
-}
+class CursorInputMapperTest : public CursorInputMapperTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        CursorInputMapperTestBase::SetUp();
+    }
+};
 
 TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) {
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
@@ -4470,328 +4297,6 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1,  1));
 }
 
-TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs motionArgs;
-    NotifyKeyArgs keyArgs;
-
-    // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
-              motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
-
-    // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
-    ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(0, motionArgs.buttonState);
-    ASSERT_NO_FATAL_FAILURE(
-            assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f));
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
-    ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
-    ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
-}
-
-TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    NotifyMotionArgs args;
-
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-/**
- * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
- * pointer acceleration or speed processing should not be applied.
- */
-TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
-                                               /*high threshold=*/100.f, /*acceleration=*/10.f);
-    mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
-    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
-    NotifyMotionArgs args;
-
-    // Move and verify scale is applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-
-    // Enable Pointer Capture
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    NotifyPointerCaptureChangedArgs captureArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
-    ASSERT_TRUE(captureArgs.request.enable);
-
-    // Move and verify scale is not applied.
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(10, args.pointerCoords[0].getX());
-    ASSERT_EQ(20, args.pointerCoords[0].getY());
-}
-
 TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
     addConfigurationProperty("cursor.mode", "pointer");
     CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
@@ -4906,104 +4411,68 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-// --- BluetoothCursorInputMapperTest ---
+// --- CursorInputMapperTestWithChoreographer ---
 
-class BluetoothCursorInputMapperTest : public CursorInputMapperTest {
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
 protected:
     void SetUp() override {
-        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
+        input_flags::enable_pointer_choreographer(true);
+        CursorInputMapperTestBase::SetUp();
     }
 };
 
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
-    addConfigurationProperty("cursor.mode", "pointer");
+TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
     CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
+    // Set up the default display.
+    prepareDisplay(ui::ROTATION_90);
 
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+    // Set up the secondary display as the display on which the pointer should be shown,
+    // and associate the InputDevice with the secondary display.
+    prepareSecondaryDisplay();
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 
-        process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-}
+    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    mFakePointerController->setPosition(100, 200);
 
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
-    addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+    // Ensure input events are generated with associated display ID but not with coords,
+    // because the coords will be decided later by PointerChoreographer.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(expectedEventTime))));
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                      WithEventTime(cappedEventTime))));
-    }
+                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
+                  WithCoords(0.0f, 0.0f))));
 }
 
-TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
-    addConfigurationProperty("cursor.mode", "pointer");
+TEST_F(CursorInputMapperTestWithChoreographer,
+       ConfigureDisplayIdShouldGenerateEventWithMismatchedPointerDisplay) {
     CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    // Set up the default display as the display on which the pointer should be shown.
+    prepareDisplay(ui::ROTATION_90);
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+
+    // Associate the InputDevice with the secondary display.
+    prepareSecondaryDisplay();
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    // With PointerChoreographer enabled, there could be a PointerController for the associated
+    // display even if it is different from the pointer display. So the mapper should generate an
+    // event.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
-    process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                  WithEventTime(expectedEventTime))));
+                  WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
+                  WithCoords(0.0f, 0.0f))));
 }
 
 // --- TouchInputMapperTest ---
diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h
index ca85558..e9c7bb4 100644
--- a/services/inputflinger/tests/InstrumentedInputReader.h
+++ b/services/inputflinger/tests/InstrumentedInputReader.h
@@ -106,6 +106,9 @@
         void setPreventingTouchpadTaps(bool prevent) override { mPreventingTouchpadTaps = prevent; }
         bool isPreventingTouchpadTaps() override { return mPreventingTouchpadTaps; }
 
+        void setLastKeyDownTimestamp(nsecs_t when) override { mLastKeyDownTimestamp = when; };
+        nsecs_t getLastKeyDownTimestamp() override { return mLastKeyDownTimestamp; };
+
     private:
         int32_t mGlobalMetaState;
         bool mUpdateGlobalMetaStateWasCalled;
@@ -113,6 +116,7 @@
         std::optional<nsecs_t> mRequestedTimeout;
         std::vector<InputDeviceInfo> mExternalStylusDevices;
         bool mPreventingTouchpadTaps{false};
+        nsecs_t mLastKeyDownTimestamp;
     } mFakeContext;
 
     friend class InputReaderTest;
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 05823cd..9de80af 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -48,7 +48,7 @@
 class MockInputReaderContext : public InputReaderContext {
 public:
     MOCK_METHOD(void, updateGlobalMetaState, (), (override));
-    int32_t getGlobalMetaState() override { return 0; };
+    MOCK_METHOD(int32_t, getGlobalMetaState, (), (override));
 
     MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override));
     MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
@@ -77,6 +77,9 @@
     MOCK_METHOD(void, setPreventingTouchpadTaps, (bool prevent), (override));
     MOCK_METHOD(bool, isPreventingTouchpadTaps, (), (override));
 
+    MOCK_METHOD(void, setLastKeyDownTimestamp, (nsecs_t when));
+    MOCK_METHOD(nsecs_t, getLastKeyDownTimestamp, ());
+
 private:
     int32_t mGeneration = 0;
 };
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 48f5673..b44529b 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -26,6 +26,7 @@
 namespace android {
 
 using testing::_;
+using testing::Args;
 using testing::DoAll;
 using testing::Return;
 using testing::SetArgPointee;
@@ -54,6 +55,7 @@
 
     void SetUp() override {
         InputMapperUnitTest::SetUp();
+        createDevice();
 
         // set key-codes expected in tests
         for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
@@ -158,4 +160,18 @@
     testTouchpadTapStateForKeys(metaKeys, /* expectPrevent= */ false);
 }
 
+TEST_F(KeyboardInputMapperUnitTest, KeyPressTimestampRecorded) {
+    nsecs_t when = ARBITRARY_TIME;
+    std::vector<int32_t> keyCodes{KEY_0, KEY_A, KEY_LEFTCTRL, KEY_RIGHTALT, KEY_LEFTSHIFT};
+    EXPECT_CALL(mMockInputReaderContext, setLastKeyDownTimestamp)
+            .With(Args<0>(when))
+            .Times(keyCodes.size());
+    for (int32_t keyCode : keyCodes) {
+        process(when, EV_KEY, keyCode, 1);
+        process(when, EV_SYN, SYN_REPORT, 0);
+        process(when, EV_KEY, keyCode, 0);
+        process(when, EV_SYN, SYN_REPORT, 0);
+    }
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
new file mode 100644
index 0000000..5e67506
--- /dev/null
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MultiTouchMotionAccumulator.h"
+#include "InputMapperTest.h"
+
+namespace android {
+
+class MultiTouchMotionAccumulatorTest : public InputMapperUnitTest {
+protected:
+    static constexpr size_t SLOT_COUNT = 8;
+
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        createDevice();
+    }
+
+    MultiTouchMotionAccumulator mMotionAccumulator;
+
+    void processMotionEvent(int32_t type, int32_t code, int32_t value) {
+        RawEvent event;
+        event.when = ARBITRARY_TIME;
+        event.readTime = READ_TIME;
+        event.deviceId = EVENTHUB_ID;
+        event.type = type;
+        event.code = code;
+        event.value = value;
+        mMotionAccumulator.process(&event);
+    }
+};
+
+TEST_F(MultiTouchMotionAccumulatorTest, ActiveSlotCountUsingSlotsProtocol) {
+    mMotionAccumulator.configure(*mDeviceContext, SLOT_COUNT, /*usingSlotsProtocol=*/true);
+    // We expect active slot count to match the touches being tracked
+    // first touch
+    processMotionEvent(EV_ABS, ABS_MT_SLOT, 0);
+    processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processMotionEvent(EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+    // second touch
+    processMotionEvent(EV_ABS, ABS_MT_SLOT, 1);
+    processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, 456);
+    processMotionEvent(EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(2u, mMotionAccumulator.getActiveSlotsCount());
+
+    // second lifted
+    processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processMotionEvent(EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+    // first lifted
+    processMotionEvent(EV_ABS, ABS_MT_SLOT, 0);
+    processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processMotionEvent(EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(0u, mMotionAccumulator.getActiveSlotsCount());
+}
+
+TEST_F(MultiTouchMotionAccumulatorTest, ActiveSlotCountNotUsingSlotsProtocol) {
+    mMotionAccumulator.configure(*mDeviceContext, SLOT_COUNT, /*usingSlotsProtocol=*/false);
+
+    // first touch
+    processMotionEvent(EV_ABS, ABS_MT_POSITION_X, 0);
+    processMotionEvent(EV_ABS, ABS_MT_POSITION_Y, 0);
+    processMotionEvent(EV_SYN, SYN_MT_REPORT, 0);
+    ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+    // second touch
+    processMotionEvent(EV_ABS, ABS_MT_POSITION_X, 50);
+    processMotionEvent(EV_ABS, ABS_MT_POSITION_Y, 50);
+    processMotionEvent(EV_SYN, SYN_MT_REPORT, 0);
+    ASSERT_EQ(2u, mMotionAccumulator.getActiveSlotsCount());
+
+    // reset
+    mMotionAccumulator.finishSync();
+    ASSERT_EQ(0u, mMotionAccumulator.getActiveSlotsCount());
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 7237424..193b84d 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -17,18 +17,71 @@
 #include "../PointerChoreographer.h"
 
 #include <gtest/gtest.h>
+#include <deque>
 #include <vector>
 
+#include "FakePointerController.h"
+#include "NotifyArgsBuilders.h"
+#include "TestEventMatchers.h"
 #include "TestInputListener.h"
 
 namespace android {
 
+using ControllerType = PointerControllerInterface::ControllerType;
+using testing::AllOf;
+
+namespace {
+
 // Helpers to std::visit with lambdas.
 template <typename... V>
-struct Visitor : V... {};
+struct Visitor : V... {
+    using V::operator()...;
+};
 template <typename... V>
 Visitor(V...) -> Visitor<V...>;
 
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+constexpr int32_t DISPLAY_ID = 5;
+constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
+                                   .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+                                   .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
+const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300);
+const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200);
+const auto TOUCHPAD_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER)
+                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+
+static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
+                                              int32_t associatedDisplayId) {
+    InputDeviceIdentifier identifier;
+
+    auto info = InputDeviceInfo();
+    info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
+                    /*isExternal=*/false, /*hasMic=*/false, associatedDisplayId);
+    info.addSource(source);
+    return info;
+}
+
+static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) {
+    std::vector<DisplayViewport> viewports;
+    for (auto displayId : displayIds) {
+        DisplayViewport viewport;
+        viewport.displayId = displayId;
+        viewport.logicalRight = DISPLAY_WIDTH;
+        viewport.logicalBottom = DISPLAY_HEIGHT;
+        viewports.push_back(viewport);
+    }
+    return viewports;
+}
+
+} // namespace
+
 // --- PointerChoreographerTest ---
 
 class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
@@ -36,7 +89,59 @@
     TestInputListener mTestListener;
     PointerChoreographer mChoreographer{mTestListener, *this};
 
-    std::shared_ptr<PointerControllerInterface> createPointerController() { return {}; }
+    std::shared_ptr<FakePointerController> assertPointerControllerCreated(
+            ControllerType expectedType) {
+        EXPECT_FALSE(mCreatedControllers.empty()) << "No PointerController was created";
+        auto [type, controller] = std::move(mCreatedControllers.front());
+        EXPECT_EQ(expectedType, type);
+        mCreatedControllers.pop_front();
+        return controller;
+    }
+
+    void assertPointerControllerNotCreated() { ASSERT_TRUE(mCreatedControllers.empty()); }
+
+    void assertPointerControllerRemoved(const std::shared_ptr<FakePointerController>& pc) {
+        // Ensure that the code under test is not holding onto this PointerController.
+        // While the policy initially creates the PointerControllers, the PointerChoreographer is
+        // expected to manage their lifecycles. Although we may not want to strictly enforce how
+        // the object is managed, in this case, we need to have a way of ensuring that the
+        // corresponding graphical resources have been released by the PointerController, and the
+        // simplest way of checking for that is to just make sure that the PointerControllers
+        // themselves are released by Choreographer when no longer in use. This check is ensuring
+        // that the reference retained by the test is the last one.
+        ASSERT_EQ(1, pc.use_count()) << "Expected PointerChoreographer to release all references "
+                                        "to this PointerController";
+    }
+
+    void assertPointerControllerNotRemoved(const std::shared_ptr<FakePointerController>& pc) {
+        // See assertPointerControllerRemoved above.
+        ASSERT_GT(pc.use_count(), 1) << "Expected PointerChoreographer to hold at least one "
+                                        "reference to this PointerController";
+    }
+
+    void assertPointerDisplayIdNotified(int32_t displayId) {
+        ASSERT_EQ(displayId, mPointerDisplayIdNotified);
+        mPointerDisplayIdNotified.reset();
+    }
+
+    void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); }
+
+private:
+    std::deque<std::pair<ControllerType, std::shared_ptr<FakePointerController>>>
+            mCreatedControllers;
+    std::optional<int32_t> mPointerDisplayIdNotified;
+
+    std::shared_ptr<PointerControllerInterface> createPointerController(
+            ControllerType type) override {
+        std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+        EXPECT_FALSE(pc->isPointerShown());
+        mCreatedControllers.emplace_back(type, pc);
+        return pc;
+    }
+
+    void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override {
+        mPointerDisplayIdNotified = displayId;
+    }
 };
 
 TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
@@ -86,4 +191,1358 @@
     }
 }
 
+TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    // Remove the mouse.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedMouse) {
+    // Just adding a viewport or device should create a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedMouse) {
+    // Without viewport information, PointerController will be created but viewport won't be set.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportNotSet();
+
+    // After Choreographer gets viewport, PointerController should also have viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // For a mouse event without a target display, default viewport should be set for
+    // the PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenDefaultMouseDisplayChangesSetsDefaultMouseViewportForPointerController) {
+    // Set one display as a default mouse display and emit mouse event to create PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    firstDisplayPc->assertViewportSet(DISPLAY_ID);
+    ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+    // Change default mouse display. Existing PointerController should be removed and a new one
+    // should be created.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
+    ASSERT_TRUE(secondDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotNotified();
+
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemovedCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenDefaultMouseDisplayChangesCallsNotifyPointerDisplayIdChanged) {
+    // Add two viewports.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+    // Set one viewport as a default mouse display ID.
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    // Set another viewport as a default mouse display ID. The mouse is moved to the other display.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest,
+       AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+    // Add two displays and set one to default.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Add two devices, one unassociated and the other associated with non-default mouse display.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+    // Set initial position for PointerControllers.
+    unassociatedMousePc->setPosition(100, 200);
+    associatedMousePc->setPosition(300, 400);
+
+    // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the status of the PointerControllers.
+    unassociatedMousePc->assertPosition(100, 200);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    associatedMousePc->assertPosition(310, 420);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+    ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+                  WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Assume that pointer capture is enabled.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/1,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+    // Notify motion as if pointer capture is enabled.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+                                     .x(10)
+                                     .y(20)
+                                     .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+                                     .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that there's no update on the PointerController.
+    pc->assertPosition(100, 200);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    // Check x-y coordinates, displayId and cursor position are not changed.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+                  WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                     AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Enable pointer capture and check if the PointerController hid the pointer.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // A mouse is connected, and the pointer is shown.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    pc->fade(PointerControllerInterface::Transition::IMMEDIATE);
+
+    // Add a second mouse is added, the pointer is shown again.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // One of the mice is removed, and it does not cause the mouse pointer to fade, because
+    // we have one more mouse connected.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotRemoved(pc);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // The final mouse is removed. The pointer is removed.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    pc->fade(PointerControllerInterface::Transition::IMMEDIATE);
+
+    // Adding a touchscreen device does not unfade the mouse pointer.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                     DISPLAY_ID)}});
+
+    ASSERT_FALSE(pc->isPointerShown());
+
+    // Show touches setting change does not unfade the mouse pointer.
+    mChoreographer.setShowTouchesEnabled(true);
+
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
+    // Disable show touches and add a touch device.
+    mChoreographer.setShowTouchesEnabled(false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    assertPointerControllerNotCreated();
+
+    // Enable show touches. PointerController still should not be created.
+    mChoreographer.setShowTouchesEnabled(true);
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchEventOccursCreatesPointerController) {
+    // Add a touch device and enable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch event. Now PointerController should be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerCreated(ControllerType::TOUCH);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenShowTouchesDisabledAndTouchEventOccursDoesNotCreatePointerController) {
+    // Add a touch device and disable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit touch event. Still, PointerController should not be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsRemovedRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Remove the device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesDisabledRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Disable show touches.
+    mChoreographer.setShowTouchesEnabled(false);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpots) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+
+    // Emit first pointer down.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit second pointer down.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 2);
+
+    // Emit second pointer up.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_UP |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit first pointer up.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 0);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                     DISPLAY_ID)}});
+
+    // Emit down event with stylus properties.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) {
+    mChoreographer.setShowTouchesEnabled(true);
+    // Add two touch devices associated to different displays.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+                                     ANOTHER_DISPLAY_ID)}});
+
+    // Emit touch event with first device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+    firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit touch events with second device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // There should be another PointerController created.
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Check if the spots are set for the second device.
+    secondDisplayPc->assertSpotCount(ANOTHER_DISPLAY_ID, 2);
+
+    // Check if there's no change on the spot of the first device.
+    firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) {
+    // Make sure the PointerController is created and there is a spot.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Reset the device and ensure the touch pointer controller was removed.
+    mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+    // Disable stylus pointer icon and add a stylus device.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    assertPointerControllerNotCreated();
+
+    // Enable stylus pointer icon. PointerController still should not be created.
+    mChoreographer.setStylusPointerIconEnabled(true);
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+    // Add a stylus device and enable stylus pointer icon.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Now PointerController should be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerCreated(ControllerType::STYLUS);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+    // Add a stylus device and disable stylus pointer icon.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Still, PointerController should not be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    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);
+
+    // Remove the device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    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);
+
+    // Disable stylus pointer icon.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+    // Set viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    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);
+
+    // Check that viewport is set for the PointerController.
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    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);
+
+    // Check that viewport is unset.
+    pc->assertViewportNotSet();
+
+    // Set viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Check that the viewport is set for the PointerController.
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    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);
+
+    // Check that viewport is unset.
+    pc->assertViewportNotSet();
+
+    // Set viewport which does not match the associated display of the stylus.
+    mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID}));
+
+    // Check that viewport is still unset.
+    pc->assertViewportNotSet();
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Emit hover enter event. This is for creating PointerController.
+    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);
+
+    // Emit hover move event. After bounds are set, PointerController will update the position.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertPosition(150, 250);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Emit hover exit event.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    // Check that the pointer is gone.
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+    mChoreographer.setStylusPointerIconEnabled(true);
+    // Add two stylus devices associated to different displays.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+    // Emit hover event with first device. This is for creating PointerController.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Emit hover event with second device. This is for creating PointerController.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // There should be another PointerController created.
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Emit hover event with first device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Check the pointer of the first device.
+    firstDisplayPc->assertPosition(150, 250);
+    ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+    // Emit hover event with second device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the pointer of the second device.
+    secondDisplayPc->assertPosition(250, 350);
+    ASSERT_TRUE(secondDisplayPc->isPointerShown());
+
+    // Check that there's no change on the pointer of the first device.
+    firstDisplayPc->assertPosition(150, 250);
+    ASSERT_TRUE(firstDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+    // Make sure the PointerController is created and there is a pointer.
+    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);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Reset the device and see the pointer controller was removed.
+    mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsAddedCreatesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedRemovesPointerController) {
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    // Remove the touchpad.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedTouchpad) {
+    // Just adding a viewport or device should not create a PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedTouchpad) {
+    // Without viewport information, PointerController will be created by a touchpad event
+    // but viewport won't be set.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportNotSet();
+
+    // After Choreographer gets viewport, PointerController should also have viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultTouchpadViewportForPointerController) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // For a touchpad event without a target display, default viewport should be set for
+    // the PointerController.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenDefaultTouchpadDisplayChangesSetsDefaultTouchpadViewportForPointerController) {
+    // Set one display as a default touchpad display and create PointerController.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    firstDisplayPc->assertViewportSet(DISPLAY_ID);
+
+    // Change default mouse display. Existing PointerController should be removed.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterTouchpadCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotNotified();
+
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedCallsNotifyPointerDisplayIdChanged) {
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenDefaultMouseDisplayChangesTouchpadCallsNotifyPointerDisplayIdChanged) {
+    // Add two viewports.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+    // Set one viewport as a default mouse display ID.
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(DISPLAY_ID);
+
+    // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
+    // before a touchpad event.
+    mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+    assertPointerControllerRemoved(firstDisplayPc);
+
+    assertPointerControllerCreated(ControllerType::MOUSE);
+    assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Notify motion with fake fingers, as if it is multi-finger swipe.
+    // Check if the position of the PointerController is added to the fake finger coords.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithCoords(0, 200), WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                   (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+                  WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                   (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+                  WithPointerCoords(2, 200, 200), WithCursorPosition(100, 200)));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-90).y(10))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
+                    .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10))
+                    .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                  WithPointerCoords(0, 10, 210), WithPointerCoords(1, 110, 210),
+                  WithPointerCoords(2, 210, 210), WithCursorPosition(100, 200)));
+}
+
+TEST_F(PointerChoreographerTest,
+       AssociatedTouchpadMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+    // Add two displays and set one to default.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Add two devices, one unassociated and the other associated with non-default mouse display.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ANOTHER_DISPLAY_ID)}});
+    auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+    // Set initial positions for PointerControllers.
+    unassociatedMousePc->setPosition(100, 200);
+    associatedMousePc->setPosition(300, 400);
+
+    // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(TOUCHPAD_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the status of the PointerControllers.
+    unassociatedMousePc->assertPosition(100, 200);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    associatedMousePc->assertPosition(310, 420);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+    ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+                  WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(200, 300);
+
+    // Assume that pointer capture is enabled.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+    // Notify motion as if pointer capture is enabled.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
+                                        .pointer(FIRST_TOUCH_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(ADISPLAY_ID_NONE)
+                                        .build());
+
+    // Check that there's no update on the PointerController.
+    pc->assertPosition(200, 300);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    // Check x-y coordinates, displayId and cursor position are not changed.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE),
+                  WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                     AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Enable pointer capture and check if the PointerController hid the pointer.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+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)}});
+    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)}});
+    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)}});
+    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, 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)}});
+    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)}});
+    auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+    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/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index ee6ff53..a3e8eaf 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -18,6 +18,7 @@
 
 #include <cmath>
 #include <compare>
+#include <ios>
 
 #include <android-base/stringprintf.h>
 #include <android/input.h>
@@ -464,9 +465,144 @@
     return WithPointersMatcher(pointers);
 }
 
-MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") {
-    *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode;
-    return arg.keyCode == keyCode;
+/// Pointer ids matcher
+class WithPointerIdsMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithPointerIdsMatcher(std::set<int32_t> pointerIds) : mPointerIds(pointerIds) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) {
+            const PointerProperties* properties = event.getPointerProperties(pointerIndex);
+            actualPointerIds.insert(properties->id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        std::set<int32_t> actualPointerIds;
+        for (const PointerProperties& properties : event.pointerProperties) {
+            actualPointerIds.insert(properties.id);
+        }
+
+        if (mPointerIds != actualPointerIds) {
+            *os << "expected pointer ids " << dumpSet(mPointerIds) << ", but got "
+                << dumpSet(actualPointerIds);
+            return false;
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with pointer ids " << dumpSet(mPointerIds); }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer ids"; }
+
+private:
+    const std::set<int32_t> mPointerIds;
+};
+
+inline WithPointerIdsMatcher WithPointerIds(const std::set<int32_t /*id*/>& pointerIds) {
+    return WithPointerIdsMatcher(pointerIds);
+}
+
+/// Key code
+class WithKeyCodeMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithKeyCodeMatcher(int32_t keyCode) : mKeyCode(keyCode) {}
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mKeyCode == args.keyCode;
+    }
+
+    bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+        return mKeyCode == event.getKeyCode();
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with key code " << KeyEvent::getLabel(mKeyCode);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong key code"; }
+
+private:
+    const int32_t mKeyCode;
+};
+
+inline WithKeyCodeMatcher WithKeyCode(int32_t keyCode) {
+    return WithKeyCodeMatcher(keyCode);
+}
+
+/// EventId
+class WithEventIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithEventIdMatcher(int32_t eventId) : mEventId(eventId) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mEventId == args.id;
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mEventId == args.id;
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mEventId == event.getId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with eventId 0x" << std::hex << mEventId; }
+
+    void DescribeNegationTo(std::ostream* os) const {
+        *os << "with eventId not equal to 0x" << std::hex << mEventId;
+    }
+
+private:
+    const int32_t mEventId;
+};
+
+inline WithEventIdMatcher WithEventId(int32_t eventId) {
+    return WithEventIdMatcher(eventId);
+}
+
+/// EventIdSource
+class WithEventIdSourceMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithEventIdSourceMatcher(IdGenerator::Source eventIdSource)
+          : mEventIdSource(eventIdSource) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(args.id);
+    }
+
+    bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(args.id);
+    }
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mEventIdSource == IdGenerator::getSource(event.getId());
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with eventId from source 0x" << std::hex << ftl::to_underlying(mEventIdSource);
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong event from source"; }
+
+private:
+    const IdGenerator::Source mEventIdSource;
+};
+
+inline WithEventIdSourceMatcher WithEventIdSource(IdGenerator::Source eventIdSource) {
+    return WithEventIdSourceMatcher(eventIdSource);
 }
 
 MATCHER_P(WithRepeatCount, repeatCount, "KeyEvent with specified repeat count") {
@@ -543,6 +679,24 @@
     return argPressure == pressure;
 }
 
+MATCHER_P(WithSize, size, "MotionEvent with specified size") {
+    const auto argSize = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE);
+    *result_listener << "expected size " << size << ", but got " << argSize;
+    return argSize == size;
+}
+
+MATCHER_P(WithOrientation, orientation, "MotionEvent with specified orientation") {
+    const auto argOrientation = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+    *result_listener << "expected orientation " << orientation << ", but got " << argOrientation;
+    return argOrientation == orientation;
+}
+
+MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") {
+    const auto argDistance = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_DISTANCE);
+    *result_listener << "expected distance " << distance << ", but got " << argDistance;
+    return argDistance == distance;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
@@ -586,6 +740,12 @@
     return arg.buttonState == buttons;
 }
 
+MATCHER_P(WithMetaState, metaState, "InputEvent with specified meta state") {
+    *result_listener << "expected meta state 0x" << std::hex << metaState << ", but got 0x"
+                     << arg.metaState;
+    return arg.metaState == metaState;
+}
+
 MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
     *result_listener << "expected action button " << actionButton << ", but got "
                      << arg.actionButton;
@@ -608,4 +768,16 @@
     return arg.xPrecision == xPrecision && arg.yPrecision == yPrecision;
 }
 
+MATCHER_P(WithPolicyFlags, policyFlags, "InputEvent with specified policy flags") {
+    *result_listener << "expected policy flags 0x" << std::hex << policyFlags << ", but got 0x"
+                     << arg.policyFlags;
+    return arg.policyFlags == static_cast<uint32_t>(policyFlags);
+}
+
+MATCHER_P(WithEdgeFlags, edgeFlags, "InputEvent with specified edge flags") {
+    *result_listener << "expected edge flags 0x" << std::hex << edgeFlags << ", but got 0x"
+                     << arg.edgeFlags;
+    return arg.edgeFlags == edgeFlags;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 6203a1d..4be1e8c 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -19,6 +19,7 @@
 #include <android-base/logging.h>
 #include <gtest/gtest.h>
 
+#include <com_android_input_flags.h>
 #include <thread>
 #include "FakePointerController.h"
 #include "InputMapperTest.h"
@@ -36,11 +37,19 @@
 constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
+constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
+constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+namespace input_flags = com::android::input::flags;
 
 /**
  * Unit tests for TouchpadInputMapper.
  */
-class TouchpadInputMapperTest : public InputMapperUnitTest {
+class TouchpadInputMapperTestBase : public InputMapperUnitTest {
 protected:
     void SetUp() override {
         InputMapperUnitTest::SetUp();
@@ -100,10 +109,19 @@
                     *outValue = 0;
                     return OK;
                 });
+        createDevice();
         mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 };
 
+class TouchpadInputMapperTest : public TouchpadInputMapperTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(false);
+        TouchpadInputMapperTestBase::SetUp();
+    }
+};
+
 /**
  * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
  * generated when hovering stops. Currently, it is not.
@@ -135,12 +153,83 @@
     setScanCodeState(KeyState::UP, {BTN_LEFT});
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
                             VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
+
+    // Liftoff
+    args.clear();
+    args += process(EV_ABS, ABS_MT_PRESSURE, 0);
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
+    args += process(EV_KEY, BTN_TOUCH, 0);
+    setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 0);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, testing::IsEmpty());
+}
+
+class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        TouchpadInputMapperTestBase::SetUp();
+    }
+};
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+//   logic can be removed.
+/**
+ * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
+ * generated when hovering stops. Currently, it is not.
+ * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
+ * but only after the button is released.
+ */
+TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) {
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+
+    std::list<NotifyArgs> args;
+
+    args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
+                                 InputReaderConfiguration::Change::DISPLAY_INFO);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
+    args += process(EV_KEY, BTN_TOUCH, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
+    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
+    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
+    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args, testing::IsEmpty());
+
+    // Without this sleep, the test fails.
+    // TODO(b/284133337): Figure out whether this can be removed
+    std::this_thread::sleep_for(std::chrono::milliseconds(20));
+
+    args += process(EV_KEY, BTN_LEFT, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_LEFT});
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    args += process(EV_KEY, BTN_LEFT, 0);
+    setScanCodeState(KeyState::UP, {BTN_LEFT});
+    args += process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
 
     // Liftoff
     args.clear();
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 9313a89..8a4f6f0 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -167,3 +167,17 @@
         "LatencyTrackerFuzzer.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "inputflinger_input_dispatcher_fuzzer",
+    defaults: [
+        "inputflinger_fuzz_defaults",
+        "libinputdispatcher_defaults",
+    ],
+    shared_libs: [
+        "libinputreporter",
+    ],
+    srcs: [
+        "InputDispatcherFuzzer.cpp",
+    ],
+}
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
new file mode 100644
index 0000000..885820f
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+namespace android {
+
+namespace {
+static constexpr int32_t MAX_RANDOM_POINTERS = 4;
+static constexpr int32_t MAX_RANDOM_DEVICES = 4;
+} // namespace
+
+int getFuzzedMotionAction(FuzzedDataProvider& fdp) {
+    int actionMasked = fdp.PickValueInArray<int>({
+            AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE,
+            AMOTION_EVENT_ACTION_HOVER_ENTER, AMOTION_EVENT_ACTION_HOVER_MOVE,
+            AMOTION_EVENT_ACTION_HOVER_EXIT, AMOTION_EVENT_ACTION_CANCEL,
+            // do not inject AMOTION_EVENT_ACTION_OUTSIDE,
+            AMOTION_EVENT_ACTION_SCROLL, AMOTION_EVENT_ACTION_POINTER_DOWN,
+            AMOTION_EVENT_ACTION_POINTER_UP,
+            // do not send buttons until verifier supports them
+            // AMOTION_EVENT_ACTION_BUTTON_PRESS,
+            // AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+    });
+    switch (actionMasked) {
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            const int32_t index = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_POINTERS - 1);
+            const int32_t action =
+                    actionMasked | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            return action;
+        }
+        default:
+            return actionMasked;
+    }
+}
+
+/**
+ * For now, focus on the 3 main sources.
+ */
+int getFuzzedSource(FuzzedDataProvider& fdp) {
+    return fdp.PickValueInArray<int>({
+            // AINPUT_SOURCE_UNKNOWN,
+            // AINPUT_SOURCE_KEYBOARD,
+            // AINPUT_SOURCE_DPAD,
+            // AINPUT_SOURCE_GAMEPAD,
+            AINPUT_SOURCE_TOUCHSCREEN, AINPUT_SOURCE_MOUSE, AINPUT_SOURCE_STYLUS,
+            // AINPUT_SOURCE_BLUETOOTH_STYLUS,
+            // AINPUT_SOURCE_TRACKBALL,
+            // AINPUT_SOURCE_MOUSE_RELATIVE,
+            // AINPUT_SOURCE_TOUCHPAD,
+            // AINPUT_SOURCE_TOUCH_NAVIGATION,
+            // AINPUT_SOURCE_JOYSTICK,
+            // AINPUT_SOURCE_HDMI,
+            // AINPUT_SOURCE_SENSOR,
+            // AINPUT_SOURCE_ROTARY_ENCODER,
+            // AINPUT_SOURCE_ANY,
+    });
+}
+
+int getFuzzedButtonState(FuzzedDataProvider& fdp) {
+    return fdp.PickValueInArray<int>({
+            0,
+            // AMOTION_EVENT_BUTTON_PRIMARY,
+            // AMOTION_EVENT_BUTTON_SECONDARY,
+            // AMOTION_EVENT_BUTTON_TERTIARY,
+            // AMOTION_EVENT_BUTTON_BACK,
+            // AMOTION_EVENT_BUTTON_FORWARD,
+            // AMOTION_EVENT_BUTTON_STYLUS_PRIMARY,
+            // AMOTION_EVENT_BUTTON_STYLUS_SECONDARY,
+    });
+}
+
+int32_t getFuzzedFlags(FuzzedDataProvider& fdp, int32_t action) {
+    constexpr std::array<int32_t, 4> FLAGS{
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
+            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+    };
+
+    int32_t flags = 0;
+    for (size_t i = 0; i < fdp.ConsumeIntegralInRange(size_t(0), FLAGS.size()); i++) {
+        flags |= fdp.PickValueInArray<int32_t>(FLAGS);
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    if (MotionEvent::getActionMasked(action) == AMOTION_EVENT_ACTION_POINTER_UP) {
+        if (fdp.ConsumeBool()) {
+            flags |= AMOTION_EVENT_FLAG_CANCELED;
+        }
+    }
+    return flags;
+}
+
+int32_t getFuzzedPointerCount(FuzzedDataProvider& fdp, int32_t action) {
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_UP: {
+            return 1;
+        }
+        case AMOTION_EVENT_ACTION_OUTSIDE:
+        case AMOTION_EVENT_ACTION_CANCEL:
+        case AMOTION_EVENT_ACTION_MOVE:
+            return fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS);
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT:
+            return 1;
+        case AMOTION_EVENT_ACTION_SCROLL:
+            return 1;
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            const uint8_t actionIndex = MotionEvent::getActionIndex(action);
+            const int32_t count =
+                    std::max(actionIndex + 1,
+                             fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS));
+            // Need to have at least 2 pointers
+            return std::max(2, count);
+        }
+        case AMOTION_EVENT_ACTION_BUTTON_PRESS:
+        case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+            return 1;
+        }
+    }
+    return 1;
+}
+
+ToolType getToolType(int32_t source) {
+    switch (source) {
+        case AINPUT_SOURCE_TOUCHSCREEN:
+            return ToolType::FINGER;
+        case AINPUT_SOURCE_MOUSE:
+            return ToolType::MOUSE;
+        case AINPUT_SOURCE_STYLUS:
+            return ToolType::STYLUS;
+    }
+    return ToolType::UNKNOWN;
+}
+
+inline nsecs_t now() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+NotifyMotionArgs generateFuzzedMotionArgs(IdGenerator& idGenerator, FuzzedDataProvider& fdp,
+                                          int32_t maxDisplays) {
+    // Create a basic motion event for testing
+    const int32_t source = getFuzzedSource(fdp);
+    const ToolType toolType = getToolType(source);
+    const int32_t action = getFuzzedMotionAction(fdp);
+    const int32_t pointerCount = getFuzzedPointerCount(fdp, action);
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+    for (int i = 0; i < pointerCount; i++) {
+        PointerProperties properties{};
+        properties.id = i;
+        properties.toolType = toolType;
+        pointerProperties.push_back(properties);
+
+        PointerCoords coords{};
+        coords.setAxisValue(AMOTION_EVENT_AXIS_X, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1);
+        pointerCoords.push_back(coords);
+    }
+
+    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
+    const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);
+
+    // Current time +- 5 seconds
+    const nsecs_t currentTime = now();
+    const nsecs_t downTime =
+            fdp.ConsumeIntegralInRange<nsecs_t>(currentTime - 5E9, currentTime + 5E9);
+    const nsecs_t readTime = downTime;
+    const nsecs_t eventTime = fdp.ConsumeIntegralInRange<nsecs_t>(downTime, downTime + 1E9);
+
+    const float cursorX = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+    const float cursorY = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+    return NotifyMotionArgs(idGenerator.nextId(), eventTime, readTime, deviceId, source, displayId,
+                            POLICY_FLAG_PASS_TO_USER, action,
+                            /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+                            getFuzzedFlags(fdp, action), AMETA_NONE, getFuzzedButtonState(fdp),
+                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                            pointerProperties.data(), pointerCoords.data(),
+                            /*xPrecision=*/0,
+                            /*yPrecision=*/0, cursorX, cursorY, downTime,
+                            /*videoFrames=*/{});
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index 3b3ed9b..deb811d 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -16,44 +16,16 @@
 
 #include <MapperHelpers.h>
 #include <fuzzer/FuzzedDataProvider.h>
+#include "FuzzedInputStream.h"
 #include "InputCommonConverter.h"
 #include "InputProcessor.h"
 
 namespace android {
 
-static constexpr int32_t MAX_AXES = 64;
+namespace {
 
-// Used by two fuzz operations and a bit lengthy, so pulled out into a function.
-NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) {
-    // Create a basic motion event for testing
-    PointerProperties properties;
-    properties.id = 0;
-    properties.toolType = getFuzzedToolType(fdp);
-    PointerCoords coords;
-    coords.clear();
-    for (int32_t i = 0; i < fdp.ConsumeIntegralInRange<int32_t>(0, MAX_AXES); i++) {
-        coords.setAxisValue(fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeFloatingPoint<float>());
-    }
+constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
 
-    const nsecs_t downTime = 2;
-    const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
-    NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                                /*eventTime=*/downTime, readTime,
-                                /*deviceId=*/fdp.ConsumeIntegral<int32_t>(), AINPUT_SOURCE_ANY,
-                                ADISPLAY_ID_DEFAULT,
-                                /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
-                                AMOTION_EVENT_ACTION_DOWN,
-                                /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
-                                /*flags=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
-                                /*buttonState=*/fdp.ConsumeIntegral<int32_t>(),
-                                MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                                /*pointerCount=*/1, &properties, &coords,
-                                /*xPrecision=*/fdp.ConsumeFloatingPoint<float>(),
-                                /*yPrecision=*/fdp.ConsumeFloatingPoint<float>(),
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
-                                /*videoFrames=*/{});
-    return motionArgs;
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
@@ -62,6 +34,7 @@
     std::unique_ptr<FuzzInputListener> mFuzzListener = std::make_unique<FuzzInputListener>();
     std::unique_ptr<InputProcessorInterface> mClassifier =
             std::make_unique<InputProcessor>(*mFuzzListener);
+    IdGenerator idGenerator(IdGenerator::Source::OTHER);
 
     while (fdp.remaining_bytes() > 0) {
         fdp.PickValueInArray<std::function<void()>>({
@@ -90,7 +63,8 @@
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyMotionArgs
-                    mClassifier->notifyMotion(generateFuzzedMotionArgs(fdp));
+                    mClassifier->notifyMotion(
+                            generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS));
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifySwitchArgs
@@ -108,7 +82,8 @@
                 },
                 [&]() -> void {
                     // InputClassifierConverterTest
-                    const NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp);
+                    const NotifyMotionArgs motionArgs =
+                            generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS);
                     aidl::android::hardware::input::common::MotionEvent motionEvent =
                             notifyMotionArgsToHalMotionEvent(motionArgs);
                 },
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
new file mode 100644
index 0000000..dc5a213
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/stringprintf.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "../FakeApplicationHandle.h"
+#include "../FakeInputDispatcherPolicy.h"
+#include "../FakeWindowHandle.h"
+#include "FuzzedInputStream.h"
+#include "dispatcher/InputDispatcher.h"
+#include "input/InputVerifier.h"
+
+namespace android {
+
+using android::base::Result;
+using android::gui::WindowInfo;
+
+namespace inputdispatcher {
+
+namespace {
+
+static constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
+static constexpr int32_t MAX_RANDOM_WINDOWS = 4;
+
+/**
+ * Provide a valid motion stream, to make the fuzzer more effective.
+ */
+class NotifyStreamProvider {
+public:
+    NotifyStreamProvider(FuzzedDataProvider& fdp)
+          : 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 =
+                verifier.processMovement(args.deviceId, args.source, args.action,
+                                         args.getPointerCount(), args.pointerProperties.data(),
+                                         args.pointerCoords.data(), args.flags);
+        if (result.ok()) {
+            return args;
+        }
+        return {};
+    }
+
+private:
+    FuzzedDataProvider& mFdp;
+
+    IdGenerator mIdGenerator;
+
+    std::map<int32_t /*displayId*/, InputVerifier> mVerifiers;
+};
+
+void scrambleWindow(FuzzedDataProvider& fdp, FakeWindowHandle& window) {
+    const int32_t left = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t top = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t width = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t height = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+
+    window.setFrame(Rect(left, top, left + width, top + height));
+    window.setSlippery(fdp.ConsumeBool());
+    window.setDupTouchToWallpaper(fdp.ConsumeBool());
+    window.setIsWallpaper(fdp.ConsumeBool());
+    window.setVisible(fdp.ConsumeBool());
+    window.setPreventSplitting(fdp.ConsumeBool());
+    const bool isTrustedOverlay = fdp.ConsumeBool();
+    window.setTrustedOverlay(isTrustedOverlay);
+    if (isTrustedOverlay) {
+        window.setSpy(fdp.ConsumeBool());
+    } else {
+        window.setSpy(false);
+    }
+}
+
+} // namespace
+
+sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
+                                          int32_t displayId) {
+    static size_t windowNumber = 0;
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, dispatcher, windowName, displayId);
+
+    scrambleWindow(fdp, *window);
+    return window;
+}
+
+void randomizeWindows(
+        std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay,
+        FuzzedDataProvider& fdp, InputDispatcher& dispatcher) {
+    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1);
+    std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId];
+
+    fdp.PickValueInArray<std::function<void()>>({
+            // Add a new window
+            [&]() -> void {
+                if (windows.size() < MAX_RANDOM_WINDOWS) {
+                    windows.push_back(generateFuzzedWindow(fdp, dispatcher, displayId));
+                }
+            },
+            // Remove a window
+            [&]() -> void {
+                if (windows.empty()) {
+                    return;
+                }
+                const int32_t erasedPosition =
+                        fdp.ConsumeIntegralInRange<int32_t>(0, windows.size() - 1);
+
+                windows.erase(windows.begin() + erasedPosition);
+                if (windows.empty()) {
+                    windowsPerDisplay.erase(displayId);
+                }
+            },
+            // Change flags or move some of the existing windows
+            [&]() -> void {
+                for (auto& window : windows) {
+                    if (fdp.ConsumeBool()) {
+                        scrambleWindow(fdp, *window);
+                    }
+                }
+            },
+    })();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+    NotifyStreamProvider streamProvider(fdp);
+
+    FakeInputDispatcherPolicy fakePolicy;
+    InputDispatcher dispatcher(fakePolicy);
+    dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+    // Start InputDispatcher thread
+    dispatcher.start();
+
+    std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
+
+    // Randomly invoke InputDispatcher api's until randomness is exhausted.
+    while (fdp.remaining_bytes() > 0) {
+        fdp.PickValueInArray<std::function<void()>>({
+                [&]() -> void {
+                    std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
+                    if (motion) {
+                        dispatcher.notifyMotion(*motion);
+                    }
+                },
+                [&]() -> void {
+                    // Scramble the windows we currently have
+                    randomizeWindows(/*byref*/ windowsPerDisplay, fdp, dispatcher);
+
+                    std::vector<WindowInfo> windowInfos;
+                    for (const auto& [displayId, windows] : windowsPerDisplay) {
+                        for (const sp<FakeWindowHandle>& window : windows) {
+                            windowInfos.emplace_back(*window->getInfo());
+                        }
+                    }
+
+                    dispatcher.onWindowInfosChanged(
+                            {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
+                },
+                // Consume on all the windows
+                [&]() -> void {
+                    for (const auto& [_, windows] : windowsPerDisplay) {
+                        for (const sp<FakeWindowHandle>& window : windows) {
+                            // To speed up the fuzzing, don't wait for consumption. If there's an
+                            // event pending, this can be consumed on the next call instead.
+                            // We also don't care about whether consumption succeeds here, or what
+                            // kind of event is returned.
+                            window->consume(0ms);
+                        }
+                    }
+                },
+        })();
+    }
+
+    dispatcher.stop();
+
+    return 0;
+}
+
+} // namespace inputdispatcher
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index bdedfdf..81c570d 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -275,6 +275,9 @@
     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 ""; }
 };
 
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
@@ -309,6 +312,10 @@
     void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; }
     void notifyStylusGestureStarted(int32_t, nsecs_t) {}
     bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); }
+    std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+            int32_t associatedDisplayId) override {
+        return {};
+    }
 };
 
 class FuzzInputListener : public virtual InputListenerInterface {
@@ -360,6 +367,12 @@
 
     void setPreventingTouchpadTaps(bool prevent) {}
     bool isPreventingTouchpadTaps() { return mFdp->ConsumeBool(); };
+
+    void setLastKeyDownTimestamp(nsecs_t when) { mLastKeyDownTimestamp = when; };
+    nsecs_t getLastKeyDownTimestamp() { return mLastKeyDownTimestamp; };
+
+private:
+    nsecs_t mLastKeyDownTimestamp;
 };
 
 template <class Fdp>
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..bd2b10a
--- /dev/null
+++ b/services/powermanager/WorkDuration.cpp
@@ -0,0 +1,52 @@
+/**
+ * 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)
+      : timestampNanos(0),
+        actualTotalDurationNanos(totalDurationNanos),
+        workPeriodStartTimestampNanos(startTimestampNanos),
+        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..26a575f
--- /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 timestampNanos;
+    int64_t actualTotalDurationNanos;
+    int64_t workPeriodStartTimestampNanos;
+    int64_t actualCpuDurationNanos;
+    int64_t actualGpuDurationNanos;
+};
+
+} // namespace android::os
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 641ba9f..3d2cf29 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -29,9 +29,12 @@
 #include <thread>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
 using android::binder::Status;
 
 using namespace android;
@@ -53,6 +56,14 @@
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 11c56a8..0dd4dd6 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -7,6 +7,18 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "sensorservice_flags",
+    package: "com.android.frameworks.sensorservice.flags",
+    srcs: ["senserservice_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "sensorservice_flags_c_lib",
+    aconfig_declarations: "dynamic_sensors_flags",
+    host_supported: true,
+}
+
 cc_library {
     name: "libsensorservice",
 
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 44d0d70..85043c9 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -91,7 +91,7 @@
 std::map<String16, int> SensorService::sPackageTargetVersion;
 Mutex SensorService::sPackageTargetVersionLock;
 String16 SensorService::sSensorInterfaceDescriptorPrefix =
-        String16("android.frameworks.sensorservice@");
+    String16("android.frameworks.sensorservice");
 AppOpsManager SensorService::sAppOpsManager;
 std::atomic_uint64_t SensorService::curProxCallbackSeq(0);
 std::atomic_uint64_t SensorService::completedCallbackSeq(0);
@@ -2295,10 +2295,12 @@
 }
 
 int SensorService::getTargetSdkVersion(const String16& opPackageName) {
-    // Don't query the SDK version for the ISensorManager descriptor as it doesn't have one. This
-    // descriptor tends to be used for VNDK clients, but can technically be set by anyone so don't
-    // give it elevated privileges.
-    if (opPackageName.startsWith(sSensorInterfaceDescriptorPrefix)) {
+    // Don't query the SDK version for the ISensorManager descriptor as it
+    // doesn't have one. This descriptor tends to be used for VNDK clients, but
+    // can technically be set by anyone so don't give it elevated privileges.
+    bool isVNDK = opPackageName.startsWith(sSensorInterfaceDescriptorPrefix) &&
+                  opPackageName.contains(String16("@"));
+    if (isVNDK) {
         return -1;
     }
 
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
index 2b6ea7c..b6acc8a 100644
--- a/services/sensorservice/aidl/SensorManager.cpp
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -188,8 +188,16 @@
 }
 
 ::android::SensorManager& SensorManagerAidl::getInternalManager() {
-    return ::android::SensorManager::getInstanceForPackage(
-            String16(ISensorManager::descriptor));
+    int32_t version;
+    ndk::ScopedAStatus status = getInterfaceVersion(&version);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Failed to get interface version with error: "
+                   << status.getDescription();
+        version = -1;
+    }
+    String16 packageName = String16(ISensorManager::descriptor);
+    packageName += String16("@") + String16(std::to_string(version).c_str());
+    return ::android::SensorManager::getInstanceForPackage(packageName);
 }
 
 /* One global looper for all event queues created from this SensorManager. */
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
new file mode 100644
index 0000000..a3bd0ee
--- /dev/null
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.frameworks.sensorservice.flags"
+
+flag {
+  name: "dynamic_sensor_hal_reconnect_handling"
+  namespace: "sensors"
+  description: "This flag controls if the dynamic sensor data will be clean up after HAL is disconnected."
+  bug: "307782607"
+}
+
+flag {
+  name: "sensor_device_on_dynamic_sensor_disconnected"
+  namespace: "sensors"
+  description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not."
+  bug: "316958439"
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 9d0f285..0989863 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -93,6 +93,7 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
+        "libsurfaceflinger_common",
         "libtimestats",
         "libtonemap",
         "libsurfaceflingerflags",
@@ -175,7 +176,6 @@
         "FrontEnd/LayerLifecycleManager.cpp",
         "FrontEnd/RequestedLayerState.cpp",
         "FrontEnd/TransactionHandler.cpp",
-        "FlagManager.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
@@ -245,6 +245,7 @@
     ],
     static_libs: [
         "android.frameworks.displayservice@1.0",
+        "libc++fs",
         "libdisplayservicehidl",
         "libserviceutils",
     ],
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 370e4b6..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,10 +61,8 @@
     ],
 }
 
-cc_library {
-    name: "libcompositionengine",
-    defaults: ["libcompositionengine_defaults"],
-    static_libs: ["libsurfaceflingerflags"],
+filegroup {
+    name: "libcompositionengine_sources",
     srcs: [
         "src/planner/CachedSet.cpp",
         "src/planner/Flattener.cpp",
@@ -86,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 {
@@ -108,8 +122,12 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
+        "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
+    shared_libs: [
+        "server_configurable_flags",
+    ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
 }
@@ -117,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",
@@ -137,18 +160,19 @@
         "tests/RenderSurfaceTest.cpp",
     ],
     static_libs: [
-        "libcompositionengine",
         "libcompositionengine_mocks",
         "libgui_mocks",
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
+        "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/AndroidTest.xml b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
new file mode 100644
index 0000000..94e92f0
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for libcompositionengine_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+                value="libcompositionengine_test->/data/local/tmp/libcompositionengine_test" />
+    </target_preparer>
+
+    <!--
+        Disable SELinux so that crashes in the test suite produces symbolized stack traces.
+    -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="libcompositionengine_test" />
+    </test>
+</configuration>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 1a8644e..18a96f4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -24,6 +24,7 @@
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputColorSetting.h>
 #include <math/mat4.h>
+#include <scheduler/interface/ICompositor.h>
 #include <ui/FenceTime.h>
 #include <ui/Transform.h>
 
@@ -89,14 +90,14 @@
     // If set, causes the dirty regions to flash with the delay
     std::optional<std::chrono::microseconds> devOptFlashDirtyRegionsDelay;
 
-    // Optional.
-    // The earliest time to send the present command to the HAL.
-    std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
+    scheduler::FrameTargets frameTargets;
 
-    // The expected time for the next present
-    nsecs_t expectedPresentTime{0};
+    // The frameInterval for the next present
+    // TODO (b/315371484): Calculate per display and store on `FrameTarget`.
+    Fps frameInterval;
 
     // If set, a frame has been scheduled for that time.
+    // TODO (b/255601557): Calculate per display.
     std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 
     std::vector<BorderRenderInfo> borderInfoList;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 5e84be1..c71c517 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -40,6 +40,9 @@
     // True if the display is secure
     virtual bool isSecure() const = 0;
 
+    // Sets the secure flag for the display
+    virtual void setSecure(bool secure) = 0;
+
     // True if the display is virtual
     virtual bool isVirtual() const = 0;
 
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 370c7cf..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;
@@ -298,14 +306,14 @@
     virtual void finishFrame(GpuCompositionResult&&) = 0;
     virtual std::optional<base::unique_fd> composeSurfaces(
             const Region&, std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
-    virtual void postFramebuffer() = 0;
+    virtual void presentFrameAndReleaseLayers() = 0;
     virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
     virtual bool chooseCompositionStrategy(
             std::optional<android::HWComposer::DeviceRequestedChanges>*) = 0;
     virtual void applyCompositionStrategy(
             const std::optional<android::HWComposer::DeviceRequestedChanges>& changes) = 0;
     virtual bool getSkipColorTransform() const = 0;
-    virtual FrameFences presentAndGetFrameFences() = 0;
+    virtual FrameFences presentFrame() = 0;
     virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, ui::Dataspace outputDataspace,
             std::vector<LayerFE*> &outLayerRef) = 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 6cf1d68..c53b461 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -59,9 +59,10 @@
             std::optional<android::HWComposer::DeviceRequestedChanges>*) override;
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override;
     bool getSkipColorTransform() const override;
-    compositionengine::Output::FrameFences presentAndGetFrameFences() override;
+    compositionengine::Output::FrameFences presentFrame() override;
     void setExpensiveRenderingExpected(bool) override;
     void finishFrame(GpuCompositionResult&&) override;
+    bool supportsOffloadPresent() const override;
 
     // compositionengine::Display overrides
     DisplayId getId() const override;
@@ -73,6 +74,7 @@
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
     void createClientCompositionCache(uint32_t cacheSize) override;
     void applyDisplayBrightness(const bool applyImmediately) override;
+    void setSecure(bool secure) override;
 
     // Internal helpers used by chooseCompositionStrategy()
     using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 229a657..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;
@@ -102,7 +104,7 @@
     std::optional<base::unique_fd> composeSurfaces(const Region&,
                                                    std::shared_ptr<renderengine::ExternalTexture>,
                                                    base::unique_fd&) override;
-    void postFramebuffer() override;
+    void presentFrameAndReleaseLayers() override;
     void renderCachedSets(const CompositionRefreshArgs&) override;
     void cacheClientCompositionRequests(uint32_t) override;
     bool canPredictCompositionStrategy(const CompositionRefreshArgs&) 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;
@@ -133,8 +136,9 @@
     };
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
-    compositionengine::Output::FrameFences presentAndGetFrameFences() override;
-    virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
+    compositionengine::Output::FrameFences presentFrame() override;
+    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 6cb1e7e..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};
 
@@ -127,6 +130,9 @@
     // The expected time for the next present
     nsecs_t expectedPresentTime{0};
 
+    // The frameInterval for the next present
+    Fps frameInterval{};
+
     // Current display brightness
     float displayBrightnessNits{-1.f};
 
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/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 7e99ec2..46cb95e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -33,6 +33,7 @@
 
     MOCK_CONST_METHOD0(getId, DisplayId());
     MOCK_CONST_METHOD0(isSecure, bool());
+    MOCK_METHOD1(setSecure, void(bool));
     MOCK_CONST_METHOD0(isVirtual, bool());
     MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t());
 
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 a56fc79..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,
@@ -118,9 +121,9 @@
                                                 base::unique_fd&));
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
-    MOCK_METHOD0(postFramebuffer, void());
+    MOCK_METHOD0(presentFrameAndReleaseLayers, void());
     MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&));
-    MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
+    MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
 
     MOCK_METHOD3(generateClientCompositionRequests,
                  std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<compositionengine::LayerFE*>&));
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 15fadbc..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,14 +144,27 @@
         }
     }
 
+    // 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();
+        }
     }
 }
 
 void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
-    std::unordered_map<compositionengine::LayerFE*, compositionengine::LayerFECompositionState*>
-            uniqueVisibleLayers;
 
     for (const auto& output : args.outputs) {
         for (auto* layer : output->getOutputLayersOrderedByZ()) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index f2acfc9..d907bf5 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);
 }
@@ -73,6 +74,10 @@
     return getState().isSecure;
 }
 
+void Display::setSecure(bool secure) {
+    editState().isSecure = secure;
+}
+
 bool Display::isVirtual() const {
     return VirtualDisplayId::tryCast(mId).has_value();
 }
@@ -245,7 +250,6 @@
     }
 
     // Get any composition changes requested by the HWC device, and apply them.
-    std::optional<android::HWComposer::DeviceRequestedChanges> changes;
     auto& hwc = getCompositionEngine().getHwComposer();
     const bool requiresClientComposition = anyLayersRequireClientComposition();
 
@@ -255,10 +259,10 @@
 
     const TimePoint hwcValidateStartTime = TimePoint::now();
 
-    if (status_t result =
-                hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
-                                                getState().earliestPresentTime,
-                                                getState().expectedPresentTime, outChanges);
+    if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
+                                                          getState().earliestPresentTime,
+                                                          getState().expectedPresentTime,
+                                                          getState().frameInterval, outChanges);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
               strerror(-result));
@@ -362,8 +366,8 @@
             static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
 }
 
-compositionengine::Output::FrameFences Display::presentAndGetFrameFences() {
-    auto fences = impl::Output::presentAndGetFrameFences();
+compositionengine::Output::FrameFences Display::presentFrame() {
+    auto fences = impl::Output::presentFrame();
 
     const auto halDisplayIdOpt = HalDisplayId::tryCast(mId);
     if (mIsDisconnected || !halDisplayIdOpt) {
@@ -430,4 +434,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 fa3733b..1c2f6cb 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -28,8 +28,11 @@
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
+#include <ftl/algorithm.h>
 #include <ftl/future.h>
 #include <gui/TraceUtils.h>
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/Time.h>
 
 #include <optional>
 #include <thread>
@@ -427,8 +430,30 @@
     uncacheBuffers(refreshArgs.bufferIdsToUncache);
 }
 
-void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
+ftl::Future<std::monostate> Output::present(
+        const compositionengine::CompositionRefreshArgs& refreshArgs) {
+    const auto stringifyExpectedPresentTime = [this, &refreshArgs]() -> std::string {
+        return ftl::Optional(getDisplayId())
+                .and_then(PhysicalDisplayId::tryCast)
+                .and_then([&refreshArgs](PhysicalDisplayId id) {
+                    return refreshArgs.frameTargets.get(id);
+                })
+                .transform([](const auto& frameTargetPtr) {
+                    return frameTargetPtr.get()->expectedPresentTime();
+                })
+                .transform([](TimePoint expectedPresentTime) {
+                    return base::StringPrintf(" vsyncIn %.2fms",
+                                              ticks<std::milli, float>(expectedPresentTime -
+                                                                       TimePoint::now()));
+                })
+                .or_else([] {
+                    // There is no vsync for this output.
+                    return std::make_optional(std::string());
+                })
+                .value();
+    };
+    ATRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
+                  stringifyExpectedPresentTime().c_str());
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
@@ -448,8 +473,26 @@
 
     devOptRepaintFlash(refreshArgs);
     finishFrame(std::move(result));
-    postFramebuffer();
+    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) {
@@ -834,8 +877,15 @@
         return;
     }
 
-    editState().earliestPresentTime = refreshArgs.earliestPresentTime;
-    editState().expectedPresentTime = refreshArgs.expectedPresentTime;
+    if (auto frameTargetPtrOpt = ftl::Optional(getDisplayId())
+                                         .and_then(PhysicalDisplayId::tryCast)
+                                         .and_then([&refreshArgs](PhysicalDisplayId id) {
+                                             return refreshArgs.frameTargets.get(id);
+                                         })) {
+        editState().earliestPresentTime = frameTargetPtrOpt->get()->earliestPresentTime();
+        editState().expectedPresentTime = frameTargetPtrOpt->get()->expectedPresentTime().ns();
+    }
+    editState().frameInterval = refreshArgs.frameInterval;
     editState().powerCallback = refreshArgs.powerCallback;
 
     compositionengine::OutputLayer* peekThroughLayer = nullptr;
@@ -1083,6 +1133,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(
@@ -1148,11 +1206,11 @@
             updateProtectedContentState();
             dequeueRenderBuffer(&bufferFence, &buffer);
             static_cast<void>(composeSurfaces(dirtyRegion, buffer, bufferFence));
-            mRenderSurface->queueBuffer(base::unique_fd());
+            mRenderSurface->queueBuffer(base::unique_fd(), getHdrSdrRatio(buffer));
         }
     }
 
-    postFramebuffer();
+    presentFrameAndReleaseLayers();
 
     std::this_thread::sleep_for(*refreshArgs.devOptFlashDirtyRegionsDelay);
 
@@ -1196,7 +1254,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() {
@@ -1204,10 +1262,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;
@@ -1262,7 +1328,7 @@
     ALOGV("hasClientComposition");
 
     renderengine::DisplaySettings clientCompositionDisplay =
-            generateClientCompositionDisplaySettings();
+            generateClientCompositionDisplaySettings(tex);
 
     // Generate the client composition requests for the layers on this output.
     auto& renderEngine = getCompositionEngine().getRenderEngine();
@@ -1343,7 +1409,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;
@@ -1363,8 +1430,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>(
@@ -1447,12 +1516,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,
@@ -1509,7 +1582,7 @@
     return false;
 }
 
-void Output::postFramebuffer() {
+void Output::presentFrameAndReleaseLayers() {
     ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
@@ -1520,7 +1593,7 @@
     auto& outputState = editState();
     outputState.dirtyRegion.clear();
 
-    auto frame = presentAndGetFrameFences();
+    auto frame = presentFrame();
 
     mRenderSurface->onPresentDisplayCompleted();
 
@@ -1590,7 +1663,7 @@
     return true;
 }
 
-compositionengine::Output::FrameFences Output::presentAndGetFrameFences() {
+compositionengine::Output::FrameFences Output::presentFrame() {
     compositionengine::Output::FrameFences result;
     if (getState().usesClientComposition) {
         result.clientTargetAcquireFence = mRenderSurface->getClientTargetAcquireFence();
@@ -1599,8 +1672,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);
     }
@@ -1615,7 +1695,7 @@
     uint64_t outputLayerHash = getState().outputLayerHash;
     editState().lastOutputLayerHash = outputLayerHash;
 
-    if (!getState().isEnabled || !mHwComposerAsyncWorker) {
+    if (!getState().isEnabled || !mPredictCompositionStrategy) {
         ALOGV("canPredictCompositionStrategy disabled");
         return false;
     }
@@ -1668,5 +1748,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..91cfe5d 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_when_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 027004a..a95a5c6 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -582,7 +582,7 @@
 TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) {
     EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
     chooseCompositionStrategy(mDisplay.get());
@@ -606,8 +606,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
-            .WillOnce(testing::DoAll(testing::SetArgPointee<4>(mDeviceRequestedChanges),
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges),
                                      Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
@@ -659,8 +659,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR)));
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
     EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1);
@@ -867,7 +867,7 @@
 }
 
 /*
- * Display::presentAndGetFrameFences()
+ * Display::presentFrame()
  */
 
 using DisplayPresentAndGetFrameFencesTest = DisplayWithLayersTestCommon;
@@ -876,7 +876,7 @@
     auto args = getDisplayCreationArgsForGpuVirtualDisplay();
     auto gpuDisplay{impl::createDisplay(mCompositionEngine, args)};
 
-    auto result = gpuDisplay->presentAndGetFrameFences();
+    auto result = gpuDisplay->presentFrame();
 
     ASSERT_TRUE(result.presentFence.get());
     EXPECT_FALSE(result.presentFence->isValid());
@@ -900,7 +900,7 @@
             .WillOnce(Return(layer2Fence));
     EXPECT_CALL(mHwComposer, clearReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID))).Times(1);
 
-    auto result = mDisplay->presentAndGetFrameFences();
+    auto result = mDisplay->presentFrame();
 
     EXPECT_EQ(presentFence, result.presentFence);
 
@@ -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;
@@ -1060,7 +1060,7 @@
     }
 };
 
-TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) {
+TEST_F(DisplayFunctionalTest, presentFrameAndReleaseLayersCriticalCallsAreOrdered) {
     InSequence seq;
 
     mDisplay->editState().isEnabled = true;
@@ -1068,7 +1068,7 @@
     EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
     EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
 
-    mDisplay->postFramebuffer();
+    mDisplay->presentFrameAndReleaseLayers();
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 6807c8e..9e35717 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -54,12 +54,13 @@
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
 
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
-    MOCK_METHOD5(getDeviceCompositionChanges,
-                 status_t(HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>,
-                          nsecs_t, 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, getDeviceCompositionChanges,
+                (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
+                 Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    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));
@@ -147,8 +148,7 @@
     MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
-    MOCK_METHOD(status_t, notifyExpectedPresentIfRequired,
-                (PhysicalDisplayId, nsecs_t, int32_t, int32_t));
+    MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
 };
 
 } // namespace mock
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 cdcb68d..30ba9e4 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;
@@ -2007,7 +2014,7 @@
         MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult());
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
-        MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     };
@@ -2029,7 +2036,7 @@
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2049,7 +2056,7 @@
     EXPECT_CALL(mOutput, prepareFrameAsync());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2895,7 +2902,7 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
         MOCK_METHOD0(prepareFrame, void());
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
@@ -2932,7 +2939,7 @@
     mOutput.mState.isEnabled = false;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2944,7 +2951,7 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion));
-    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2959,8 +2966,8 @@
     EXPECT_CALL(mOutput, updateProtectedContentState());
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _));
-    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
-    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2978,7 +2985,7 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
                      bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
@@ -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,19 +3090,19 @@
                 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));
 }
 
 /*
- * Output::postFramebuffer()
+ * Output::presentFrameAndReleaseLayers()
  */
 
 struct OutputPostFramebufferTest : public testing::Test {
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
+        MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
     };
 
     struct Layer {
@@ -3104,7 +3142,7 @@
 TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) {
     mOutput.mState.isEnabled = false;
 
-    mOutput.postFramebuffer();
+    mOutput.presentFrameAndReleaseLayers();
 }
 
 TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) {
@@ -3119,10 +3157,10 @@
     // setup below are satisfied in the specific order.
     InSequence seq;
 
-    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
-    mOutput.postFramebuffer();
+    mOutput.presentFrameAndReleaseLayers();
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
@@ -3141,7 +3179,7 @@
     frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
     frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
 
-    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
     // Compare the pointers values of each fence to make sure the correct ones
@@ -3164,7 +3202,7 @@
                 EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get());
             });
 
-    mOutput.postFramebuffer();
+    mOutput.presentFrameAndReleaseLayers();
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
@@ -3177,7 +3215,7 @@
     frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
     frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
 
-    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
     // Fence::merge is called, and since none of the fences are actually valid,
@@ -3187,7 +3225,7 @@
     EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return());
     EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return());
 
-    mOutput.postFramebuffer();
+    mOutput.presentFrameAndReleaseLayers();
 }
 
 TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
@@ -3212,7 +3250,7 @@
     Output::FrameFences frameFences;
     frameFences.presentFence = presentFence;
 
-    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
     // Each released layer should be given the presentFence.
@@ -3232,7 +3270,7 @@
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
 
-    mOutput.postFramebuffer();
+    mOutput.presentFrameAndReleaseLayers();
 
     // After the call the list of released layers should have been cleared.
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
@@ -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() {
@@ -3962,7 +4094,11 @@
 };
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = false;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -3976,7 +4112,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
 
@@ -3998,7 +4138,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4011,7 +4155,11 @@
 }
 
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
-    mOutput.mState.isSecure = true;
+    if (FlagManager::getInstance().display_protected()) {
+        mOutput.mState.isProtected = true;
+    } else {
+        mOutput.mState.isSecure = true;
+    }
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
@@ -4090,6 +4238,7 @@
 
     GenerateClientCompositionRequestsTest() {
         mOutput.mState.needsFiltering = false;
+        mOutput.mState.isProtected = true;
 
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
@@ -4114,6 +4263,7 @@
         mOutput.mState.displaySpace.setOrientation(kDisplayOrientation);
         mOutput.mState.needsFiltering = false;
         mOutput.mState.isSecure = false;
+        mOutput.mState.isProtected = true;
 
         for (size_t i = 0; i < mLayers.size(); i++) {
             mLayers[i].mOutputLayerState.clearClientTarget = false;
@@ -4576,7 +4726,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4589,7 +4739,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4602,7 +4752,7 @@
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
@@ -4780,7 +4930,7 @@
             Region(Rect(0, 0, 1000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
@@ -4799,7 +4949,7 @@
             Region(Rect(1000, 0, 2000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
-            true,  /* supports protected content */
+            true,  /* isProtected */
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
@@ -4900,5 +5050,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 00590e6..d2eff75 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>
@@ -223,9 +226,27 @@
 }
 
 TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    auto& layerState2 = mTestLayers[1]->layerState;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+    };
+
+    initializeFlattener(layers);
+
     mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
     mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
 
+    expectAllLayersFlattened(layers);
+}
+
+TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      true);
+
     auto& layerState1 = mTestLayers[0]->layerState;
     auto& layerState2 = mTestLayers[1]->layerState;
 
@@ -235,7 +256,14 @@
     };
 
     initializeFlattener(layers);
-    expectAllLayersFlattened(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) {
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 5b6591a..799d62c 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -63,14 +63,16 @@
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mCompositionDisplay{args.compositionDisplay},
-        mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())),
-        mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())),
-        mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())),
+        mPendingModeFpsTrace(concatId("PendingModeFps")),
+        mActiveModeFpsTrace(concatId("ActiveModeFps")),
+        mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
-        mRefreshRateSelector(std::move(args.refreshRateSelector)) {
+        mRefreshRateSelector(std::move(args.refreshRateSelector)),
+        mHasDesiredModeTrace(concatId("HasDesiredMode"), false) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
+    mCompositionDisplay->editState().isProtected = args.isProtected;
     mCompositionDisplay->createRenderSurface(
             compositionengine::RenderSurfaceCreationArgsBuilder()
                     .setDisplayWidth(ANativeWindow_getWidth(args.nativeWindow.get()))
@@ -209,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) {
@@ -354,6 +353,10 @@
     return mCompositionDisplay->isSecure();
 }
 
+void DisplayDevice::setSecure(bool secure) {
+    mCompositionDisplay->setSecure(secure);
+}
+
 const Rect DisplayDevice::getBounds() const {
     return mCompositionDisplay->getState().displaySpace.getBoundsAsRect();
 }
@@ -523,59 +526,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 a044534..97b56a2 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;
@@ -94,6 +95,7 @@
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
     bool isSecure() const;
+    void setSecure(bool secure);
 
     int getWidth() const;
     int getHeight() const;
@@ -185,39 +187,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 +208,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)
@@ -269,6 +250,11 @@
     void dump(utils::Dumper&) const;
 
 private:
+    template <size_t N>
+    inline std::string concatId(const char (&str)[N]) const {
+        return std::string(ftl::Concat(str, ' ', getId().value).str());
+    }
+
     const sp<SurfaceFlinger> mFlinger;
     HWComposer& mHwComposer;
     const wp<IBinder> mDisplayToken;
@@ -277,9 +263,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;
@@ -314,12 +300,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) =
-            {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false};
+    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;
 };
 
@@ -348,6 +333,7 @@
     uint32_t height = 0;
     std::string displayName;
     bool isSecure = false;
+    bool isProtected = false;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
     Fps requestedRefreshRate;
 
@@ -369,6 +355,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 1643ad0..64a8ae7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -24,6 +24,7 @@
 #include <android-base/file.h>
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
+#include <common/FlagManager.h>
 #include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <utils/Trace.h>
@@ -77,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;
@@ -173,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();
     }
 
@@ -215,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;
 };
@@ -263,7 +271,10 @@
             }
         }
     }
-
+    if (getLayerLifecycleBatchCommand()) {
+        mEnableLayerCommandBatchingFlag =
+                FlagManager::getInstance().enable_layer_command_batching();
+    }
     ALOGI("Loaded AIDL composer3 HAL service");
 }
 
@@ -280,8 +291,8 @@
     }
 }
 
-bool AidlComposer::getDisplayConfigurationsSupported() const {
-    return mComposerInterfaceVersion >= 3;
+bool AidlComposer::isVrrSupported() const {
+    return mComposerInterfaceVersion >= 3 && FlagManager::getInstance().vrr_config();
 }
 
 std::vector<Capability> AidlComposer::getCapabilities() {
@@ -399,25 +410,58 @@
 
 Error AidlComposer::createLayer(Display display, Layer* outLayer) {
     int64_t layer;
-    const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
-                                                         kMaxLayerBufferCount, &layer);
-    if (!status.isOk()) {
-        ALOGE("createLayer failed %s", status.getDescription().c_str());
-        return static_cast<Error>(status.getServiceSpecificError());
+    Error error = Error::NONE;
+    if (!mEnableLayerCommandBatchingFlag) {
+        const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
+                                                             kMaxLayerBufferCount, &layer);
+        if (!status.isOk()) {
+            ALOGE("createLayer failed %s", status.getDescription().c_str());
+            return static_cast<Error>(status.getServiceSpecificError());
+        }
+    } else {
+        // generate a unique layerID. map in AidlComposer with <SF_layerID, HWC_layerID>
+        // Add this as a new displayCommand in execute command.
+        // return the SF generated layerID instead of calling HWC
+        layer = mLayerID++;
+        mMutex.lock_shared();
+        if (auto writer = getWriter(display)) {
+            writer->get().setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+                                                            translate<int64_t>(layer),
+                                                            LayerLifecycleBatchCommandType::CREATE);
+            writer->get().setNewBufferSlotCount(translate<int64_t>(display),
+                                                translate<int64_t>(layer), kMaxLayerBufferCount);
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
     }
-
     *outLayer = translate<Layer>(layer);
-    return Error::NONE;
+    return error;
 }
 
 Error AidlComposer::destroyLayer(Display display, Layer layer) {
-    const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
-                                                          translate<int64_t>(layer));
-    if (!status.isOk()) {
-        ALOGE("destroyLayer failed %s", status.getDescription().c_str());
-        return static_cast<Error>(status.getServiceSpecificError());
+    Error error = Error::NONE;
+    if (!mEnableLayerCommandBatchingFlag) {
+        const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
+                                                              translate<int64_t>(layer));
+        if (!status.isOk()) {
+            ALOGE("destroyLayer failed %s", status.getDescription().c_str());
+            return static_cast<Error>(status.getServiceSpecificError());
+        }
+    } else {
+        mMutex.lock_shared();
+        if (auto writer = getWriter(display)) {
+            writer->get()
+                    .setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+                                                       translate<int64_t>(layer),
+                                                       LayerLifecycleBatchCommandType::DESTROY);
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
     }
-    return Error::NONE;
+
+    return error;
 }
 
 Error AidlComposer::getActiveConfig(Display display, Config* outConfig) {
@@ -583,6 +627,13 @@
     return Error::NONE;
 }
 
+bool AidlComposer::getLayerLifecycleBatchCommand() {
+    std::vector<Capability> capabilities = getCapabilities();
+    bool hasCapability = std::find(capabilities.begin(), capabilities.end(),
+                                   Capability::LAYER_LIFECYCLE_BATCH_COMMAND) != capabilities.end();
+    return hasCapability;
+}
+
 Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) {
     const auto status = mAidlComposerClient->getOverlaySupport(outProperties);
     if (!status.isOk()) {
@@ -658,7 +709,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;
@@ -671,7 +723,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;
     }
@@ -749,7 +801,8 @@
 }
 
 Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime,
-                                    uint32_t* outNumTypes, uint32_t* outNumRequests) {
+                                    int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                    uint32_t* outNumRequests) {
     const auto displayId = translate<int64_t>(display);
     ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
 
@@ -758,7 +811,8 @@
     auto writer = getWriter(display);
     auto reader = getReader(display);
     if (writer && reader) {
-        writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime});
+        writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime},
+                                      frameIntervalNs);
         error = execute(display);
     } else {
         error = Error::BAD_DISPLAY;
@@ -776,8 +830,9 @@
 }
 
 Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                             int* outPresentFence, uint32_t* state) {
+                                             int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                             uint32_t* outNumRequests, int* outPresentFence,
+                                             uint32_t* state) {
     const auto displayId = translate<int64_t>(display);
     ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
 
@@ -787,7 +842,8 @@
     auto reader = getReader(display);
     if (writer && reader) {
         writer->get().presentOrvalidateDisplay(displayId,
-                                               ClockMonotonicTimestamp{expectedPresentTime});
+                                               ClockMonotonicTimestamp{expectedPresentTime},
+                                               frameIntervalNs);
         error = execute(display);
     } else {
         error = Error::BAD_DISPLAY;
@@ -1570,8 +1626,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);
@@ -1581,10 +1636,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 7693a80..ea0e53a 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -66,7 +66,7 @@
     ~AidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
-    bool getDisplayConfigurationsSupported() const;
+    bool isVrrSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
@@ -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,
@@ -134,12 +135,13 @@
 
     Error setClientTargetSlotCount(Display display) override;
 
-    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                          uint32_t* outNumRequests) override;
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                          uint32_t* outNumTypes, uint32_t* outNumRequests) override;
 
     Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                   int* outPresentFence, uint32_t* state) override;
+                                   int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                   uint32_t* outNumRequests, int* outPresentFence,
+                                   uint32_t* state) override;
 
     Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
     /* see setClientTarget for the purpose of slot */
@@ -260,7 +262,7 @@
     void removeDisplay(Display) EXCLUDES(mMutex);
     void addReader(Display) REQUIRES(mMutex);
     void removeReader(Display) REQUIRES(mMutex);
-
+    bool getLayerLifecycleBatchCommand();
     bool hasMultiThreadedPresentSupport(Display);
 
     // 64KiB minus a small space for metadata such as read/write pointers
@@ -291,6 +293,8 @@
     ftl::SharedMutex mMutex;
 
     int32_t mComposerInterfaceVersion = 1;
+    bool mEnableLayerCommandBatchingFlag = false;
+    std::atomic<int64_t> mLayerID = 1;
 
     // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
     sp<GraphicBuffer> mClearSlotBuffer;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 6704d88..bc067a0 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -105,7 +105,7 @@
     };
 
     virtual bool isSupported(OptionalFeature) const = 0;
-    virtual bool getDisplayConfigurationsSupported() const = 0;
+    virtual bool isVrrSupported() const = 0;
 
     virtual std::vector<aidl::android::hardware::graphics::composer3::Capability>
     getCapabilities() = 0;
@@ -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,
@@ -174,11 +175,13 @@
     virtual Error setClientTargetSlotCount(Display display) = 0;
 
     virtual Error validateDisplay(Display display, nsecs_t expectedPresentTime,
-                                  uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
+                                  int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                  uint32_t* outNumRequests) = 0;
 
     virtual Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                           uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                           int* outPresentFence, uint32_t* state) = 0;
+                                           int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                           uint32_t* outNumRequests, int* outPresentFence,
+                                           uint32_t* state) = 0;
 
     virtual Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) = 0;
     /* see setClientTarget for the purpose of slot */
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 1775a7a..ba0825c 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -29,9 +29,9 @@
 
 #include <scheduler/Fps.h>
 
+#include <common/FlagManager.h>
 #include "DisplayHardware/Hal.h"
 #include "Scheduler/StrongTyping.h"
-#include "Utils/FlagUtils.h"
 
 namespace android {
 
@@ -50,7 +50,6 @@
 
 using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>;
 using DisplayModeIterator = DisplayModes::const_iterator;
-using namespace com::android::graphics::surfaceflinger;
 
 class DisplayMode {
 public:
@@ -140,7 +139,7 @@
     // Peak refresh rate represents the highest refresh rate that can be used
     // for the presentation.
     Fps getPeakFps() const {
-        return flagutils::vrrConfigEnabled() && mVrrConfig
+        return FlagManager::getInstance().vrr_config() && mVrrConfig
                 ? Fps::fromPeriodNsecs(mVrrConfig->minFrameIntervalNs)
                 : mVsyncRate;
     }
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 0c2b77d..704ece5 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -165,8 +165,7 @@
     auto intError = mComposer.getChangedCompositionTypes(
             mId, &layerIds, &types);
     uint32_t numElements = layerIds.size();
-    auto error = static_cast<Error>(intError);
-    error = static_cast<Error>(intError);
+    const auto error = static_cast<Error>(intError);
     if (error != Error::NONE) {
         return error;
     }
@@ -446,12 +445,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);
 }
 
@@ -517,11 +517,12 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+Error Display::validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
                         uint32_t* outNumRequests) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
-    auto intError = mComposer.validateDisplay(mId, expectedPresentTime, &numTypes, &numRequests);
+    auto intError = mComposer.validateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+                                              &numRequests);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
@@ -532,14 +533,15 @@
     return error;
 }
 
-Error Display::presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                 uint32_t* outNumRequests, sp<android::Fence>* outPresentFence,
-                                 uint32_t* state) {
+Error Display::presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                 uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                 sp<android::Fence>* outPresentFence, uint32_t* state) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
     int32_t presentFenceFd = -1;
-    auto intError = mComposer.presentOrValidateDisplay(mId, expectedPresentTime, &numTypes,
-                                                       &numRequests, &presentFenceFd, state);
+    auto intError =
+            mComposer.presentOrValidateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+                                               &numRequests, &presentFenceFd, state);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 23dd3e5..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;
@@ -148,9 +151,10 @@
             const android::sp<android::Fence>& releaseFence) = 0;
     [[nodiscard]] virtual hal::Error setPowerMode(hal::PowerMode mode) = 0;
     [[nodiscard]] virtual hal::Error setVsyncEnabled(hal::Vsync enabled) = 0;
-    [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                              uint32_t* outNumRequests) = 0;
+    [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                              uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
     [[nodiscard]] virtual hal::Error presentOrValidate(nsecs_t expectedPresentTime,
+                                                       int32_t frameIntervalNs,
                                                        uint32_t* outNumTypes,
                                                        uint32_t* outNumRequests,
                                                        android::sp<android::Fence>* outPresentFence,
@@ -226,17 +230,17 @@
     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>&,
                                const android::sp<android::Fence>& releaseFence) override;
     hal::Error setPowerMode(hal::PowerMode) override;
     hal::Error setVsyncEnabled(hal::Vsync enabled) override;
-    hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+    hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
                         uint32_t* outNumRequests) override;
-    hal::Error presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                 uint32_t* outNumRequests,
+    hal::Error presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                 uint32_t* outNumTypes, uint32_t* outNumRequests,
                                  android::sp<android::Fence>* outPresentFence,
                                  uint32_t* state) override;
     ftl::Future<hal::Error> setDisplayBrightness(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index fb6089d..cf1c3c1 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <chrono>
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -76,6 +77,8 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::VrrConfig;
+using namespace std::string_literals;
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
@@ -88,7 +91,8 @@
       : mComposer(std::move(composer)),
         mMaxVirtualDisplayDimension(static_cast<size_t>(sysprop::max_virtual_display_dimension(0))),
         mUpdateDeviceProductInfoOnHotplugReconnect(
-                sysprop::update_device_product_info_on_hotplug_reconnect(false)) {}
+                sysprop::update_device_product_info_on_hotplug_reconnect(false)),
+        mEnableVrrTimeout(base::GetBoolProperty("debug.sf.vrr_timeout_hint_enabled"s, false)) {}
 
 HWComposer::HWComposer(const std::string& composerServiceName)
       : HWComposer(Hwc2::Composer::create(composerServiceName)) {}
@@ -268,7 +272,7 @@
 
     const auto hwcDisplayId = mDisplayData.at(displayId).hwcDisplay->getId();
 
-    if (mComposer->getDisplayConfigurationsSupported()) {
+    if (mComposer->isVrrSupported()) {
         return getModesFromDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs);
     }
 
@@ -298,6 +302,10 @@
             hwcMode.dpiY = config.dpi->y;
         }
 
+        if (!mEnableVrrTimeout) {
+            hwcMode.vrrConfig->notifyExpectedPresentConfig = {};
+        }
+
         modes.push_back(hwcMode);
     }
 
@@ -432,12 +440,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;
 }
@@ -445,7 +453,7 @@
 status_t HWComposer::getDeviceCompositionChanges(
         HalDisplayId displayId, bool frameUsesClientComposition,
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-        nsecs_t expectedPresentTime,
+        nsecs_t expectedPresentTime, Fps frameInterval,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
     ATRACE_CALL();
 
@@ -485,12 +493,12 @@
     }();
 
     displayData.validateWasSkipped = false;
-    displayData.lastExpectedPresentTimestamp = expectedPresentTime;
+    ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
-        sp<Fence> outPresentFence;
+        sp<Fence> outPresentFence = Fence::NO_FENCE;
         uint32_t state = UINT32_MAX;
-        error = hwcDisplay->presentOrValidate(expectedPresentTime, &numTypes, &numRequests,
-                                              &outPresentFence, &state);
+        error = hwcDisplay->presentOrValidate(expectedPresentTime, frameInterval.getPeriodNsecs(),
+                                              &numTypes, &numRequests, &outPresentFence, &state);
         if (!hasChangesError(error)) {
             RETURN_IF_HWC_ERROR_FOR("presentOrValidate", error, displayId, UNKNOWN_ERROR);
         }
@@ -505,7 +513,8 @@
         }
         // Present failed but Validate ran.
     } else {
-        error = hwcDisplay->validate(expectedPresentTime, &numTypes, &numRequests);
+        error = hwcDisplay->validate(expectedPresentTime, frameInterval.getPeriodNsecs(), &numTypes,
+                                     &numRequests);
     }
     ALOGV("SkipValidate failed, Falling back to SLOW validate/present");
     if (!hasChangesError(error)) {
@@ -878,23 +887,15 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
-                                                     nsecs_t expectedPresentTime,
-                                                     int32_t frameIntervalNs, int32_t timeoutNs) {
+status_t HWComposer::notifyExpectedPresent(PhysicalDisplayId displayId,
+                                           TimePoint expectedPresentTime, Fps frameInterval) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-
-    auto& displayData = mDisplayData[displayId];
-    if (expectedPresentTime >= displayData.lastExpectedPresentTimestamp &&
-        expectedPresentTime < displayData.lastExpectedPresentTimestamp + timeoutNs) {
-        return NO_ERROR;
-    }
-
-    displayData.lastExpectedPresentTimestamp = expectedPresentTime;
-    ATRACE_FORMAT("%s ExpectedPresentTime %" PRId64 " frameIntervalNs %d", __func__,
-                  expectedPresentTime, frameIntervalNs);
-    const auto error = mComposer->notifyExpectedPresent(displayData.hwcDisplay->getId(),
-                                                        expectedPresentTime, frameIntervalNs);
-
+    ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+                  ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+                  ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+    const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
+                                                        expectedPresentTime.ns(),
+                                                        frameInterval.getPeriodNsecs());
     if (error != hal::Error::NONE) {
         ALOGE("Error in notifyExpectedPresent call %s", to_string(error).c_str());
         return INVALID_OPERATION;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 726a8ea..fb32ff4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -147,10 +147,12 @@
     virtual status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-            nsecs_t expectedPresentTime, std::optional<DeviceRequestedChanges>* outChanges) = 0;
+            nsecs_t expectedPresentTime, Fps frameInterval,
+            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(
@@ -301,9 +303,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) = 0;
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
-    virtual status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, nsecs_t expectedPresentTime,
-                                                     int32_t frameIntervalNs,
-                                                     int32_t timeoutNs) = 0;
+    virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                           Fps frameInterval) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -346,11 +347,12 @@
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-            nsecs_t expectedPresentTime,
+            nsecs_t expectedPresentTime, Fps frameInterval,
             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(
@@ -462,8 +464,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) override;
     status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override;
-    status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, nsecs_t expectedPresentTime,
-                                             int32_t frameIntervalNs, int32_t timeoutNs) override;
+    status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                   Fps frameInterval) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -497,8 +499,6 @@
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         nsecs_t lastPresentTimestamp = 0;
 
-        nsecs_t lastExpectedPresentTimestamp = 0;
-
         std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
 
         bool validateWasSkipped;
@@ -543,6 +543,7 @@
 
     const size_t mMaxVirtualDisplayDimension;
     const bool mUpdateDeviceProductInfoOnHotplugReconnect;
+    bool mEnableVrrTimeout;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 20f7548..e3d9622 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;
@@ -167,10 +169,8 @@
         out << "}, ";
         out << "notifyExpectedPresentConfig={";
         if (vrrConfig->notifyExpectedPresentConfig) {
-            out << "notifyExpectedPresentHeadsUpNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
-                << ", notifyExpectedPresentTimeoutNs="
-                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+            out << "headsUpNs=" << vrrConfig->notifyExpectedPresentConfig->headsUpNs
+                << ", timeoutNs=" << vrrConfig->notifyExpectedPresentConfig->timeoutNs;
         }
         out << "}}";
         return out.str();
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c13e568..c4ff9cc 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();
     }
 
@@ -269,8 +276,8 @@
     }
 }
 
-bool HidlComposer::getDisplayConfigurationsSupported() const {
-    // getDisplayConfigurations is not supported on the HIDL composer.
+bool HidlComposer::isVrrSupported() const {
+    // VRR is not supported on the HIDL composer.
     return false;
 };
 
@@ -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;
@@ -665,7 +673,8 @@
 }
 
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
-                                    uint32_t* outNumTypes, uint32_t* outNumRequests) {
+                                    int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+                                    uint32_t* outNumRequests) {
     ATRACE_NAME("HwcValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.validateDisplay();
@@ -681,8 +690,9 @@
 }
 
 Error HidlComposer::presentOrValidateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
-                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                             int* outPresentFence, uint32_t* state) {
+                                             int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+                                             uint32_t* outNumRequests, int* outPresentFence,
+                                             uint32_t* state) {
     ATRACE_NAME("HwcPresentOrValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentOrvalidateDisplay();
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 1004ddd..d78bfb7 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -167,7 +167,7 @@
     ~HidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
-    bool getDisplayConfigurationsSupported() const;
+    bool isVrrSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
@@ -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,
@@ -236,12 +237,13 @@
 
     Error setClientTargetSlotCount(Display display) override;
 
-    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                          uint32_t* outNumRequests) override;
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                          uint32_t* outNumTypes, uint32_t* outNumRequests) override;
 
     Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                   int* outPresentFence, uint32_t* state) override;
+                                   int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                   uint32_t* outNumRequests, int* outPresentFence,
+                                   uint32_t* state) override;
 
     Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
     /* see setClientTarget for the purpose of slot */
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index f00ef67..a0c943b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,10 +31,6 @@
 #include <utils/Mutex.h>
 #include <utils/Trace.h>
 
-#include <aidl/android/hardware/power/IPower.h>
-#include <aidl/android/hardware/power/IPowerHintSession.h>
-#include <aidl/android/hardware/power/WorkDuration.h>
-
 #include <binder/IServiceManager.h>
 
 #include "../SurfaceFlingerProperties.h"
@@ -50,7 +46,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 +139,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 +159,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 +192,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 +205,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 +225,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 +247,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 +268,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 +306,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 +315,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..bbe51cc0 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -25,9 +25,14 @@
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
-#include <compositionengine/impl/OutputCompositionState.h>
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
+
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <scheduler/Time.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
@@ -46,16 +51,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 +67,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 +98,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 +119,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 +135,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 +230,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 +253,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 +273,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/FlagManager.cpp b/services/surfaceflinger/FlagManager.cpp
deleted file mode 100644
index f8ad8f6..0000000
--- a/services/surfaceflinger/FlagManager.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2021 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 "FlagManager.h"
-
-#include <SurfaceFlingerProperties.sysprop.h>
-#include <android-base/parsebool.h>
-#include <android-base/parseint.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <renderengine/RenderEngine.h>
-#include <server_configurable_flags/get_flags.h>
-#include <cinttypes>
-
-namespace android {
-static constexpr const char* kExperimentNamespace = "surface_flinger_native_boot";
-static constexpr const int64_t kDemoFlag = -1;
-
-FlagManager::~FlagManager() = default;
-
-void FlagManager::dump(std::string& result) const {
-    base::StringAppendF(&result, "FlagManager values: \n");
-    base::StringAppendF(&result, "demo_flag: %" PRId64 "\n", demo_flag());
-    base::StringAppendF(&result, "use_adpf_cpu_hint: %s\n", use_adpf_cpu_hint() ? "true" : "false");
-    base::StringAppendF(&result, "use_skia_tracing: %s\n", use_skia_tracing() ? "true" : "false");
-}
-
-namespace {
-template <typename T>
-std::optional<T> doParse(const char* str);
-
-template <>
-[[maybe_unused]] std::optional<int32_t> doParse(const char* str) {
-    int32_t ret;
-    return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
-}
-
-template <>
-[[maybe_unused]] std::optional<int64_t> doParse(const char* str) {
-    int64_t ret;
-    return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
-}
-
-template <>
-[[maybe_unused]] std::optional<bool> doParse(const char* str) {
-    base::ParseBoolResult parseResult = base::ParseBool(str);
-    switch (parseResult) {
-        case base::ParseBoolResult::kTrue:
-            return std::make_optional(true);
-        case base::ParseBoolResult::kFalse:
-            return std::make_optional(false);
-        case base::ParseBoolResult::kError:
-            return std::nullopt;
-    }
-}
-} // namespace
-
-std::string FlagManager::getServerConfigurableFlag(const std::string& experimentFlagName) const {
-    return server_configurable_flags::GetServerConfigurableFlag(kExperimentNamespace,
-                                                                experimentFlagName, "");
-}
-
-template int32_t FlagManager::getValue<int32_t>(const std::string&, std::optional<int32_t>,
-                                                int32_t) const;
-template int64_t FlagManager::getValue<int64_t>(const std::string&, std::optional<int64_t>,
-                                                int64_t) const;
-template bool FlagManager::getValue<bool>(const std::string&, std::optional<bool>, bool) const;
-template <typename T>
-T FlagManager::getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
-                        T defaultValue) const {
-    // System property takes precedence over the experiment config server value.
-    if (systemPropertyOpt.has_value()) {
-        return *systemPropertyOpt;
-    }
-    std::string str = getServerConfigurableFlag(experimentFlagName);
-    return str.empty() ? defaultValue : doParse<T>(str.c_str()).value_or(defaultValue);
-}
-
-int64_t FlagManager::demo_flag() const {
-    std::optional<int64_t> sysPropVal = std::nullopt;
-    return getValue("DemoFeature__demo_flag", sysPropVal, kDemoFlag);
-}
-
-bool FlagManager::use_adpf_cpu_hint() const {
-    std::optional<bool> sysPropVal =
-            doParse<bool>(base::GetProperty("debug.sf.enable_adpf_cpu_hint", "").c_str());
-    return getValue("AdpfFeature__adpf_cpu_hint", sysPropVal, false);
-}
-
-bool FlagManager::use_skia_tracing() const {
-    std::optional<bool> sysPropVal =
-            doParse<bool>(base::GetProperty(PROPERTY_SKIA_ATRACE_ENABLED, "").c_str());
-    return getValue("SkiaTracingFeature__use_skia_tracing", sysPropVal, false);
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/FlagManager.h b/services/surfaceflinger/FlagManager.h
deleted file mode 100644
index e834142..0000000
--- a/services/surfaceflinger/FlagManager.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 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 <cstdint>
-#include <optional>
-#include <string>
-
-namespace android {
-// Manages flags for SurfaceFlinger, including default values, system properties, and Mendel
-// experiment configuration values.
-class FlagManager {
-public:
-    FlagManager() = default;
-    virtual ~FlagManager();
-    void dump(std::string& result) const;
-
-    int64_t demo_flag() const;
-
-    bool use_adpf_cpu_hint() const;
-
-    bool use_skia_tracing() const;
-
-private:
-    friend class FlagManagerTest;
-
-    // Wrapper for mocking in test.
-    virtual std::string getServerConfigurableFlag(const std::string& experimentFlagName) const;
-
-    template <typename T>
-    T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
-               T defaultValue) const;
-};
-} // namespace android
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 2d4ec04..29c9432 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -28,6 +28,7 @@
     ],
     static_libs: [
         "libperfetto_client_experimental",
+        "libsurfaceflinger_common",
     ],
     export_include_dirs: ["."],
 }
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 1b1307b..d0e2d7a 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -21,6 +21,7 @@
 #include "FrameTimeline.h"
 
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
@@ -279,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) {
@@ -376,6 +390,22 @@
     mGpuComposition = true;
 }
 
+// TODO(b/316171339): migrate from perfetto side
+bool SurfaceFrame::isSelfJanky() const {
+    int32_t jankType = getJankType().value_or(JankType::None);
+
+    if (jankType == JankType::None) {
+        return false;
+    }
+
+    int32_t jankBitmask = JankType::AppDeadlineMissed | JankType::Unknown;
+    if (jankType & jankBitmask) {
+        return true;
+    }
+
+    return false;
+}
+
 std::optional<int32_t> SurfaceFrame::getJankType() const {
     std::scoped_lock lock(mMutex);
     if (mPresentState == PresentState::Dropped) {
@@ -388,6 +418,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);
@@ -504,10 +543,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;
     }
@@ -518,6 +558,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;
     }
@@ -542,6 +583,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;
     }
@@ -612,6 +658,7 @@
         mJankType = JankType::Dropped;
         // Since frame was not presented, lets drop any present value
         mActuals.presentTime = 0;
+        mJankSeverityType = JankSeverityType::Unknown;
     }
 }
 
@@ -624,7 +671,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
@@ -717,6 +764,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
@@ -909,6 +957,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) {
@@ -940,6 +989,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;
     }
@@ -1074,6 +1128,70 @@
     });
 }
 
+void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                                  nsecs_t previousPredictionPresentTime) const {
+    nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
+    const constexpr float kThresh = 0.5f;
+    const constexpr float kRange = 1.5f;
+    for (auto& surfaceFrame : mSurfaceFrames) {
+        if (previousPredictionPresentTime != 0 &&
+            static_cast<float>(mSurfaceFlingerPredictions.presentTime -
+                               previousPredictionPresentTime) >=
+                    static_cast<float>(mRenderRate.getPeriodNsecs()) * kRange &&
+            static_cast<float>(surfaceFrame->getPredictions().presentTime) <=
+                    (static_cast<float>(mSurfaceFlingerPredictions.presentTime) -
+                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
+            static_cast<float>(surfaceFrame->getPredictions().presentTime) >=
+                    (static_cast<float>(previousPredictionPresentTime) -
+                     kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
+            // sf skipped frame is not considered if app is self janked
+            !surfaceFrame->isSelfJanky()) {
+            skippedFrameStartTime = surfaceFrame->getPredictions().endTime;
+            skippedFramePresentTime = surfaceFrame->getPredictions().presentTime;
+            break;
+        }
+    }
+
+    // add slice
+    if (skippedFrameStartTime != 0 && skippedFramePresentTime != 0) {
+        int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+
+        // Actual timeline start
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(static_cast<uint64_t>(skippedFrameStartTime + monoBootOffset));
+
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
+
+            actualDisplayFrameStartEvent->set_cookie(actualTimelineCookie);
+
+            actualDisplayFrameStartEvent->set_token(0);
+            actualDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
+            actualDisplayFrameStartEvent->set_on_time_finish(mFrameReadyMetadata ==
+                                                             FrameReadyMetadata::OnTimeFinish);
+            actualDisplayFrameStartEvent->set_gpu_composition(false);
+            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
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
+
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualDisplayFrameEndEvent = event->set_frame_end();
+
+            actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+        });
+    }
+}
+
 void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
                                                nsecs_t monoBootOffset) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
@@ -1099,6 +1217,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
@@ -1115,17 +1234,18 @@
     });
 }
 
-void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const {
+nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                           nsecs_t previousPredictionPresentTime) const {
     if (mSurfaceFrames.empty()) {
         // We don't want to trace display frames without any surface frames updates as this cannot
         // be janky
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // DisplayFrame should not have an invalid token.
         ALOGE("Cannot trace DisplayFrame with invalid token");
-        return;
+        return previousPredictionPresentTime;
     }
 
     if (mPredictionState == PredictionState::Valid) {
@@ -1138,6 +1258,11 @@
     for (auto& surfaceFrame : mSurfaceFrames) {
         surfaceFrame->trace(mToken, monoBootOffset);
     }
+
+    if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime);
+    }
+    return mSurfaceFlingerPredictions.presentTime;
 }
 
 float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
@@ -1228,8 +1353,9 @@
         const auto& pendingPresentFence = *mPendingPresentFences.begin();
         const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset);
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
@@ -1244,9 +1370,10 @@
         }
 
         auto& displayFrame = pendingPresentFence.second;
-        displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset);
-        mPreviousPresentTime = signalTime;
+        displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                                             mPreviousPredictionPresentTime);
+        mPreviousActualPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
         --i;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 538ea12..a76f7d4 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -165,9 +165,12 @@
                  TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode);
     ~SurfaceFrame() = default;
 
+    bool isSelfJanky() const;
+
     // 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 +235,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 +255,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.
@@ -378,7 +383,8 @@
         // Emits a packet for perfetto tracing. The function body will be executed only if tracing
         // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
         // and SYSTEM_TIME_MONOTONIC.
-        void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+        nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                      nsecs_t previousPredictionPresentTime) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                         std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
@@ -403,6 +409,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;
         }
@@ -411,6 +418,8 @@
         void dump(std::string& result, nsecs_t baseTime) const;
         void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
         void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+        void addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                             nsecs_t previousActualPresentTime) const;
         void classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
                           nsecs_t previousPresentTime);
 
@@ -432,6 +441,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
@@ -499,7 +510,8 @@
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
-    nsecs_t mPreviousPresentTime = 0;
+    nsecs_t mPreviousActualPresentTime = 0;
+    nsecs_t mPreviousPredictionPresentTime = 0;
     const JankClassificationThresholds mJankClassificationThresholds;
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
     // The initial container size for the vector<SurfaceFrames> inside display frame. Although
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 1e5a6fb..821ac0c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -190,8 +190,12 @@
     return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID;
 }
 
-LayerHierarchyBuilder::LayerHierarchyBuilder(
-        const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+void LayerHierarchyBuilder::init(const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+    mLayerIdToHierarchy.clear();
+    mHierarchies.clear();
+    mRoot = nullptr;
+    mOffscreenRoot = nullptr;
+
     mHierarchies.reserve(layers.size());
     mLayerIdToHierarchy.reserve(layers.size());
     for (auto& layer : layers) {
@@ -202,6 +206,7 @@
         onLayerAdded(layer.get());
     }
     detachHierarchyFromRelativeParent(&mOffscreenRoot);
+    mInitialized = true;
 }
 
 void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) {
@@ -332,7 +337,7 @@
     }
 }
 
-void LayerHierarchyBuilder::update(
+void LayerHierarchyBuilder::doUpdate(
         const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
         const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) {
     // rebuild map
@@ -381,6 +386,32 @@
     attachHierarchyToRelativeParent(&mRoot);
 }
 
+void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) {
+    if (!mInitialized) {
+        ATRACE_NAME("LayerHierarchyBuilder:init");
+        init(layerLifecycleManager.getLayers());
+    } else if (layerLifecycleManager.getGlobalChanges().test(
+                       RequestedLayerState::Changes::Hierarchy)) {
+        ATRACE_NAME("LayerHierarchyBuilder:update");
+        doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers());
+    } else {
+        return; // nothing to do
+    }
+
+    uint32_t invalidRelativeRoot;
+    bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    while (hasRelZLoop) {
+        ATRACE_NAME("FixRelZLoop");
+        TransactionTraceWriter::getInstance().invoke("relz_loop_detected",
+                                                     /*overwrite=*/false);
+        layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+        // reinitialize the hierarchy with the updated layer data
+        init(layerLifecycleManager.getLayers());
+        // check if we have any remaining loops
+        hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
+    }
+}
+
 const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const {
     return mRoot;
 }
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index ba2e262..a1c73c3 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "RequestedLayerState.h"
 #include "ftl/small_vector.h"
 
@@ -197,9 +198,8 @@
 // hierarchy from a list of RequestedLayerState and associated change flags.
 class LayerHierarchyBuilder {
 public:
-    LayerHierarchyBuilder(const std::vector<std::unique_ptr<RequestedLayerState>>&);
-    void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
-                const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
+    LayerHierarchyBuilder() = default;
+    void update(LayerLifecycleManager& layerLifecycleManager);
     LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const;
     const LayerHierarchy& getHierarchy() const;
     const LayerHierarchy& getOffscreenHierarchy() const;
@@ -213,14 +213,18 @@
     void detachFromRelativeParent(LayerHierarchy*);
     void attachHierarchyToRelativeParent(LayerHierarchy*);
     void detachHierarchyFromRelativeParent(LayerHierarchy*);
-
+    void init(const std::vector<std::unique_ptr<RequestedLayerState>>&);
+    void doUpdate(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+                  const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
     void onLayerDestroyed(RequestedLayerState* layer);
     void updateMirrorLayer(RequestedLayerState* layer);
     LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true);
+
     std::unordered_map<uint32_t, LayerHierarchy*> mLayerIdToHierarchy;
     std::vector<std::unique_ptr<LayerHierarchy>> mHierarchies;
     LayerHierarchy mRoot{nullptr};
     LayerHierarchy mOffscreenRoot{nullptr};
+    bool mInitialized = false;
 };
 
 } // namespace android::surfaceflinger::frontend
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/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 7a85da0..38974a2 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -332,6 +332,14 @@
     return geomBufferSize.toFloatRect();
 }
 
+bool LayerSnapshot::isFrontBuffered() const {
+    if (!externalTexture) {
+        return false;
+    }
+
+    return externalTexture->getUsage() & AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
+}
+
 Hwc2::IComposerClient::BlendMode LayerSnapshot::getBlendMode(
         const RequestedLayerState& requested) const {
     auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 4fd6495..73ee22f 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -143,6 +143,7 @@
     std::string getIsVisibleReason() const;
     bool hasInputInfo() const;
     FloatRect sourceBounds() const;
+    bool isFrontBuffered() const;
     Hwc2::IComposerClient::BlendMode getBlendMode(const RequestedLayerState& requested) const;
     friend std::ostream& operator<<(std::ostream& os, const LayerSnapshot& obj);
     void merge(const RequestedLayerState& requested, bool forceUpdate, bool displayChanges,
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 2a0857d..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;
@@ -739,6 +739,7 @@
         !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
         if (forceUpdate ||
             snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+                                 RequestedLayerState::Changes::BufferSize |
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
@@ -813,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;
@@ -827,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) {
@@ -1034,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;
@@ -1057,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);
 
@@ -1095,6 +1103,8 @@
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
     }
 
+    snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
+
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
     if (path.isClone()) {
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 700baa2..c8b1059 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -136,6 +136,7 @@
 using gui::GameMode;
 using gui::LayerMetadata;
 using gui::WindowInfo;
+using ui::Size;
 
 using PresentState = frametimeline::SurfaceFrame::PresentState;
 
@@ -165,6 +166,7 @@
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
+    mDrawingState.previousFrameNumber = 0;
     mDrawingState.barrierFrameNumber = 0;
     mDrawingState.producerId = 0;
     mDrawingState.barrierProducerId = 0;
@@ -191,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.
@@ -1271,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
@@ -1716,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, ' ');
     }
@@ -2591,6 +2602,9 @@
         }
     }
 
+    Rect bufferSize = getBufferSize(getDrawingState());
+    info.contentSize = Size(bufferSize.width(), bufferSize.height());
+
     return info;
 }
 
@@ -2673,6 +2687,7 @@
 }
 
 void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
+    if (mFlinger->mLayerLifecycleManagerEnabled) return;
     mSnapshot->path.id = clonedFrom->getSequence();
     mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
 
@@ -2931,7 +2946,6 @@
             break;
         }
     }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
@@ -2940,6 +2954,10 @@
     if (mBufferInfo.mBuffer) {
         mPreviouslyPresentedLayerStacks.push_back(layerStack);
     }
+
+    if (mDrawingState.frameNumber > 0) {
+        mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+    }
 }
 
 void Layer::onSurfaceFrameCreated(
@@ -3144,6 +3162,7 @@
 void Layer::resetDrawingStateBufferInfo() {
     mDrawingState.producerId = 0;
     mDrawingState.frameNumber = 0;
+    mDrawingState.previousFrameNumber = 0;
     mDrawingState.releaseBufferListener = nullptr;
     mDrawingState.buffer = nullptr;
     mDrawingState.acquireFence = sp<Fence>::make(-1);
@@ -3246,7 +3265,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;
         }
@@ -3279,7 +3298,7 @@
             }
         }
 
-        if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
+        if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
             return static_cast<nsecs_t>(0);
         }
 
@@ -3420,6 +3439,7 @@
             // If this transaction set an acquire fence on this layer, set its acquire time
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
+            handle->previousFrameNumber = mDrawingState.previousFrameNumber;
 
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
@@ -3625,7 +3645,7 @@
             // to upsert RenderEngine's caches. Put in a special workaround to be backwards
             // compatible with old vendors, with a ticking clock.
             static const int32_t kVendorVersion =
-                    base::GetIntProperty("ro.vndk.version", __ANDROID_API_FUTURE__);
+                    base::GetIntProperty("ro.board.api_level", __ANDROID_API_FUTURE__);
             if (const auto format =
                         static_cast<aidl::android::hardware::graphics::common::PixelFormat>(
                                 mBufferInfo.mBuffer->getPixelFormat());
@@ -4024,10 +4044,10 @@
     return getAlpha() > 0.0f || hasBlur();
 }
 
-void Layer::onPostComposition(const DisplayDevice* display,
-                              const std::shared_ptr<FenceTime>& glDoneFence,
-                              const std::shared_ptr<FenceTime>& presentFence,
-                              const CompositorTiming& compositorTiming) {
+void Layer::onCompositionPresented(const DisplayDevice* display,
+                                   const std::shared_ptr<FenceTime>& glDoneFence,
+                                   const std::shared_ptr<FenceTime>& presentFence,
+                                   const CompositorTiming& compositorTiming) {
     // mFrameLatencyNeeded is true when a new frame was latched for the
     // composition.
     if (!mBufferInfo.mFrameLatencyNeeded) return;
@@ -4230,6 +4250,14 @@
     return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace;
 }
 
+bool Layer::isFrontBuffered() const {
+    if (mBufferInfo.mBuffer == nullptr) {
+        return false;
+    }
+
+    return mBufferInfo.mBuffer->getUsage() & AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
+}
+
 ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) {
     ui::Dataspace updatedDataspace = dataspace;
     // translate legacy dataspaces to modern dataspaces
@@ -4308,7 +4336,6 @@
         prepareBasicGeometryCompositionState();
         prepareGeometryCompositionState();
         snapshot->roundedCorner = getRoundedCornerState();
-        snapshot->stretchEffect = getStretchEffect();
         snapshot->transformedBounds = mScreenBounds;
         if (mEffectiveShadowRadius > 0.f) {
             snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings;
@@ -4414,7 +4441,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 dd91adc..c772e0e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -138,6 +138,7 @@
         ui::Dataspace dataspace;
 
         uint64_t frameNumber;
+        uint64_t previousFrameNumber;
         // high watermark framenumber to use to check for barriers to protect ourselves
         // from out of order transactions
         uint64_t barrierFrameNumber;
@@ -342,6 +343,8 @@
     //
     ui::Dataspace getDataSpace() const;
 
+    virtual bool isFrontBuffered() const;
+
     virtual sp<LayerFE> getCompositionEngineLayerFE() const;
     virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
     sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
@@ -433,13 +436,10 @@
     void updateCloneBufferInfo();
     uint64_t mPreviousFrameNumber = 0;
 
-    /*
-     * called after composition.
-     * returns true if the layer latched a new buffer this frame.
-     */
-    void onPostComposition(const DisplayDevice*, const std::shared_ptr<FenceTime>& /*glDoneFence*/,
-                           const std::shared_ptr<FenceTime>& /*presentFence*/,
-                           const CompositorTiming&);
+    void onCompositionPresented(const DisplayDevice*,
+                                const std::shared_ptr<FenceTime>& /*glDoneFence*/,
+                                const std::shared_ptr<FenceTime>& /*presentFence*/,
+                                const CompositorTiming&);
 
     // If a buffer was replaced this frame, release the former buffer
     void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/);
@@ -915,14 +915,13 @@
     void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&, nsecs_t now);
     void recordLayerHistoryAnimationTx(const scheduler::LayerProps&, nsecs_t now);
     auto getLayerProps() const {
-        return scheduler::LayerProps{
-                .visible = isVisible(),
-                .bounds = getBounds(),
-                .transform = getTransform(),
-                .setFrameRateVote = getFrameRateForLayerTree(),
-                .frameRateSelectionPriority = getFrameRateSelectionPriority(),
-                .isSmallDirty = mSmallDirty,
-        };
+        return scheduler::LayerProps{.visible = isVisible(),
+                                     .bounds = getBounds(),
+                                     .transform = getTransform(),
+                                     .setFrameRateVote = getFrameRateForLayerTree(),
+                                     .frameRateSelectionPriority = getFrameRateSelectionPriority(),
+                                     .isSmallDirty = mSmallDirty,
+                                     .isFrontBuffered = isFrontBuffered()};
     };
     bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
     void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
@@ -1179,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/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 42676c6..b960e33 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,10 +16,10 @@
 
 #include <algorithm>
 
+#include <common/FlagManager.h>
 #include "Client.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
-#include "Utils/FlagUtils.h"
 
 #include <SkSurface.h>
 
@@ -268,7 +268,7 @@
 }
 
 void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
-    if (mFeatures.test(Features::RenderRate) && mVsyncRate && flagutils::vrrConfigEnabled()) {
+    if (mFeatures.test(Features::RenderRate) && mVsyncRate && FlagManager::getInstance().misc1()) {
         mRenderFps = renderFps;
         const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
         createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
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/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 9a55c94..c80c8fd 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -43,6 +43,7 @@
 #include <utils/Errors.h>
 #include <utils/Trace.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "FrameTimeline.h"
@@ -99,6 +100,11 @@
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
             return StringPrintf("ModeChanged{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(), event.modeChange.modeId);
+        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+            return StringPrintf("HdcpLevelsChange{displayId=%s, connectedLevel=%d, maxLevel=%d}",
+                                to_string(event.header.displayId).c_str(),
+                                event.hdcpLevelsChange.connectedLevel,
+                                event.hdcpLevelsChange.maxLevel);
         default:
             return "Event{}";
     }
@@ -169,6 +175,20 @@
             }};
 }
 
+DisplayEventReceiver::Event makeHdcpLevelsChange(PhysicalDisplayId displayId,
+                                                 int32_t connectedLevel, int32_t maxLevel) {
+    return DisplayEventReceiver::Event{
+            .header =
+                    DisplayEventReceiver::Event::Header{
+                            .type = DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE,
+                            .displayId = displayId,
+                            .timestamp = systemTime(),
+                    },
+            .hdcpLevelsChange.connectedLevel = connectedLevel,
+            .hdcpLevelsChange.maxLevel = maxLevel,
+    };
+}
+
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
@@ -300,7 +320,7 @@
 
     mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
                                .readyDuration = mReadyDuration.count(),
-                               .earliestVsync = mLastVsyncCallbackTime.ns()});
+                               .lastVsync = mLastVsyncCallbackTime.ns()});
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
@@ -308,7 +328,7 @@
     auto connection = sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
                                                       IPCThreadState::self()->getCallingUid(),
                                                       eventRegistration);
-    if (flags::misc1()) {
+    if (FlagManager::getInstance().misc1()) {
         const int policy = SCHED_FIFO;
         connection->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
     }
@@ -441,6 +461,14 @@
     mCondition.notify_all();
 }
 
+void EventThread::onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeHdcpLevelsChange(displayId, connectedLevel, maxLevel));
+    mCondition.notify_all();
+}
+
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -500,7 +528,7 @@
             const auto scheduleResult =
                     mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                                  .readyDuration = mReadyDuration.count(),
-                                                 .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                                 .lastVsync = mLastVsyncCallbackTime.ns()});
             LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
         } else {
             mVsyncRegistration.cancel();
@@ -556,6 +584,9 @@
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
             return true;
 
+        case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+            return true;
+
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
             return connection->mEventRegistration.test(
                     gui::ISurfaceComposer::EventRegistration::modeChanged);
@@ -756,7 +787,7 @@
     if (reschedule) {
         mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                      .readyDuration = mReadyDuration.count(),
-                                     .earliestVsync = mLastVsyncCallbackTime.ns()});
+                                     .lastVsync = mLastVsyncCallbackTime.ns()});
     }
     return oldRegistration;
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7842318..8970103 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -131,6 +131,9 @@
             const sp<EventThreadConnection>& connection) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
+
+    virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                                     int32_t maxLevel) = 0;
 };
 
 struct IEventThreadCallback {
@@ -177,6 +180,9 @@
 
     void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
 
+    void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
+                             int32_t maxLevel) override;
+
 private:
     friend EventThreadTest;
 
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/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
index cb9bfe9..82af61a 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "FrameRateOverrideMappings.h"
+#include <common/FlagManager.h>
 
 namespace android::scheduler {
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
@@ -30,7 +31,7 @@
         }
     }
 
-    {
+    if (!FlagManager::getInstance().game_default_frame_rate()) {
         const auto iter = mFrameRateOverridesFromGameManager.find(uid);
         if (iter != mFrameRateOverridesFromGameManager.end()) {
             return iter->second;
@@ -61,10 +62,13 @@
     for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
         overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
     }
-    for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
-        if (std::find_if(overrides.begin(), overrides.end(),
-                         [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
-            overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+
+    if (!FlagManager::getInstance().game_default_frame_rate()) {
+        for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
+            if (std::find_if(overrides.begin(), overrides.end(),
+                             [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
+                overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+            }
         }
     }
 
@@ -93,7 +97,9 @@
     if (!hasOverrides) return;
 
     dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent);
-    dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager);
+    if (!FlagManager::getInstance().game_default_frame_rate()) {
+        dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager);
+    }
     dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor);
 }
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 069d89b..5ce883c 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -31,6 +31,7 @@
 #include <string>
 #include <utility>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
 #include "LayerInfo.h"
@@ -40,12 +41,21 @@
 namespace {
 
 bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
-    // Layers with an explicit frame rate or frame rate category are always kept active,
+    if (FlagManager::getInstance().misc1() && !info.isVisible()) {
+        return false;
+    }
+
+    // Layers with an explicit frame rate or frame rate category are kept active,
     // but ignore NoVote.
     if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
         return true;
     }
 
+    // Make all front buffered layers active
+    if (FlagManager::getInstance().vrr_config() && info.isFrontBuffered() && info.isVisible()) {
+        return true;
+    }
+
     return info.isVisible() && info.getLastUpdatedTime() >= threshold;
 }
 
@@ -157,6 +167,27 @@
     info->setDefaultLayerVote(getVoteType(frameRateCompatibility, contentDetectionEnabled));
 }
 
+void LayerHistory::setLayerProperties(int32_t id, const LayerProps& properties) {
+    std::lock_guard lock(mLock);
+
+    auto [found, layerPair] = findLayer(id);
+    if (found == LayerStatus::NotFound) {
+        // Offscreen layer
+        ALOGV("%s: %d not registered", __func__, id);
+        return;
+    }
+
+    const auto& info = layerPair->second;
+    info->setProperties(properties);
+
+    // Activate layer if inactive and visible.
+    if (found == LayerStatus::LayerInInactiveMap && info->isVisible()) {
+        mActiveLayerInfos.insert(
+                {id, std::make_pair(layerPair->first, std::move(layerPair->second))});
+        mInactiveLayerInfos.erase(id);
+    }
+}
+
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
     ATRACE_CALL();
     Summary summary;
@@ -235,6 +266,7 @@
         if (isLayerActive(*info, threshold)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
+
             const auto voteType = [&]() {
                 switch (frameRate.vote.type) {
                     case Layer::FrameRateCompatibility::Default:
@@ -247,15 +279,45 @@
                         return LayerVoteType::NoVote;
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
+                    case Layer::FrameRateCompatibility::Gte:
+                        return LayerVoteType::ExplicitGte;
                 }
             }();
 
-            if (frameRate.isValid()) {
-                const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
-                info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
-                                    frameRate.category});
+            if (FlagManager::getInstance().game_default_frame_rate()) {
+                // Determine the layer frame rate considering the following priorities:
+                // 1. Game mode intervention frame rate override
+                // 2. setFrameRate vote
+                // 3. Game default frame rate override
+
+                const auto& [gameModeFrameRateOverride, gameDefaultFrameRateOverride] =
+                        getGameFrameRateOverrideLocked(info->getOwnerUid());
+
+                const auto gameFrameRateOverrideVoteType =
+                        info->isVisible() ? LayerVoteType::ExplicitDefault : LayerVoteType::NoVote;
+
+                const auto setFrameRateVoteType =
+                        info->isVisible() ? voteType : LayerVoteType::NoVote;
+
+                if (gameModeFrameRateOverride.isValid()) {
+                    info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
+                } else if (frameRate.isValid()) {
+                    info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
+                                        frameRate.vote.seamlessness, frameRate.category});
+                } else if (gameDefaultFrameRateOverride.isValid()) {
+                    info->setLayerVote(
+                            {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
+                } else {
+                    info->resetLayerVote();
+                }
             } else {
-                info->resetLayerVote();
+                if (frameRate.isValid()) {
+                    const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
+                    info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
+                                        frameRate.category});
+                } else {
+                    info->resetLayerVote();
+                }
             }
 
             it++;
@@ -314,4 +376,56 @@
     return isSmallDirty;
 }
 
+void LayerHistory::updateGameModeFrameRateOverride(FrameRateOverride frameRateOverride) {
+    const uid_t uid = frameRateOverride.uid;
+    std::lock_guard lock(mLock);
+    if (frameRateOverride.frameRateHz != 0.f) {
+        mGameFrameRateOverride[uid].first = Fps::fromValue(frameRateOverride.frameRateHz);
+    } else {
+        if (mGameFrameRateOverride[uid].second.getValue() == 0.f) {
+            mGameFrameRateOverride.erase(uid);
+        } else {
+            mGameFrameRateOverride[uid].first = Fps();
+        }
+    }
+}
+
+void LayerHistory::updateGameDefaultFrameRateOverride(FrameRateOverride frameRateOverride) {
+    const uid_t uid = frameRateOverride.uid;
+    std::lock_guard lock(mLock);
+    if (frameRateOverride.frameRateHz != 0.f) {
+        mGameFrameRateOverride[uid].second = Fps::fromValue(frameRateOverride.frameRateHz);
+    } else {
+        if (mGameFrameRateOverride[uid].first.getValue() == 0.f) {
+            mGameFrameRateOverride.erase(uid);
+        } else {
+            mGameFrameRateOverride[uid].second = Fps();
+        }
+    }
+}
+
+std::pair<Fps, Fps> LayerHistory::getGameFrameRateOverride(uid_t uid) const {
+    if (!FlagManager::getInstance().game_default_frame_rate()) {
+        return std::pair<Fps, Fps>();
+    }
+
+    std::lock_guard lock(mLock);
+
+    return getGameFrameRateOverrideLocked(uid);
+}
+
+std::pair<Fps, Fps> LayerHistory::getGameFrameRateOverrideLocked(uid_t uid) const {
+    if (!FlagManager::getInstance().game_default_frame_rate()) {
+        return std::pair<Fps, Fps>();
+    }
+
+    const auto it = mGameFrameRateOverride.find(uid);
+
+    if (it == mGameFrameRateOverride.end()) {
+        return std::pair<Fps, Fps>(Fps(), Fps());
+    }
+
+    return it->second;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index bac1ec6..930d06c 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -43,6 +43,7 @@
 
 class LayerHistory {
 public:
+    using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
     using LayerVoteType = RefreshRateSelector::LayerVoteType;
     static constexpr std::chrono::nanoseconds kMaxPeriodForHistory = 1s;
 
@@ -73,7 +74,7 @@
     // does not set a preference for refresh rate.
     void setDefaultFrameRateCompatibility(int32_t id, FrameRateCompatibility frameRateCompatibility,
                                           bool contentDetectionEnabled);
-
+    void setLayerProperties(int32_t id, const LayerProps&);
     using Summary = std::vector<RefreshRateSelector::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
@@ -89,6 +90,15 @@
 
     bool isSmallDirtyArea(uint32_t dirtyArea, float threshold) const;
 
+    // Updates the frame rate override set by game mode intervention
+    void updateGameModeFrameRateOverride(FrameRateOverride frameRateOverride) EXCLUDES(mLock);
+
+    // Updates the frame rate override set by game default frame rate
+    void updateGameDefaultFrameRateOverride(FrameRateOverride frameRateOverride) EXCLUDES(mLock);
+
+    std::pair<Fps, Fps> getGameFrameRateOverride(uid_t uid) const EXCLUDES(mLock);
+    std::pair<Fps, Fps> getGameFrameRateOverrideLocked(uid_t uid) const REQUIRES(mLock);
+
 private:
     friend class LayerHistoryTest;
     friend class LayerHistoryIntegrationTest;
@@ -137,6 +147,13 @@
 
     // Whether a mode change is in progress or not
     std::atomic<bool> mModeChangePending = false;
+
+    // A list to look up the game frame rate overrides
+    // Each entry includes:
+    // 1. the uid of the app
+    // 2. a pair of game mode intervention frame frame and game default frame rate override
+    // set to 0.0 if there is no such override
+    std::map<uid_t, std::pair<Fps, Fps>> mGameFrameRateOverride GUARDED_BY(mLock);
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 36f2475..9c4f7a5 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -62,6 +62,10 @@
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
+            if (FlagManager::getInstance().vrr_config()) {
+                break;
+            }
+            FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
@@ -75,6 +79,10 @@
     }
 }
 
+void LayerInfo::setProperties(const android::scheduler::LayerProps& properties) {
+    *mLayerProps = properties;
+}
+
 bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
     return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
                                           mFrameTimeValidSince.time_since_epoch())
@@ -272,17 +280,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(),
@@ -304,19 +311,22 @@
 
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
         if (mLayerVote.category != FrameRateCategory::Default) {
-            ATRACE_FORMAT_INSTANT("ExplicitCategory (%s)",
+            const auto voteType = mLayerVote.type == LayerHistory::LayerVoteType::NoVote
+                    ? LayerHistory::LayerVoteType::NoVote
+                    : LayerHistory::LayerVoteType::ExplicitCategory;
+            ATRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
                                   ftl::enum_string(mLayerVote.category).c_str());
-            ALOGV("%s uses frame rate category: %d", mName.c_str(),
-                  static_cast<int>(mLayerVote.category));
-            votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, Fps(),
-                             Seamlessness::Default, mLayerVote.category,
+            ALOGV("%s voted %s with category: %s", mName.c_str(),
+                  ftl::enum_string(voteType).c_str(),
+                  ftl::enum_string(mLayerVote.category).c_str());
+            votes.push_back({voteType, Fps(), Seamlessness::Default, mLayerVote.category,
                              mLayerVote.categorySmoothSwitchOnly});
         }
 
         if (mLayerVote.fps.isValid() ||
             mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
             ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
-            ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
+            ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type));
             votes.push_back(mLayerVote);
         }
 
@@ -331,6 +341,14 @@
         return votes;
     }
 
+    // Vote for max refresh rate whenever we're front-buffered.
+    if (FlagManager::getInstance().vrr_config() && isFrontBuffered()) {
+        ATRACE_FORMAT_INSTANT("front buffered");
+        ALOGV("%s is front-buffered", mName.c_str());
+        votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+        return votes;
+    }
+
     const LayerInfo::Frequent frequent = isFrequent(now);
     mIsFrequencyConclusive = frequent.isConclusive;
     if (!frequent.isFrequent) {
@@ -391,6 +409,10 @@
     return mLayerProps->frameRateSelectionPriority;
 }
 
+bool LayerInfo::isFrontBuffered() const {
+    return mLayerProps->isFrontBuffered;
+}
+
 FloatRect LayerInfo::getBounds() const {
     return mLayerProps->bounds;
 }
@@ -413,7 +435,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()) {
@@ -428,11 +451,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(),
@@ -440,8 +463,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()) {
@@ -453,7 +487,7 @@
         ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
-    return consistent;
+    return consistent ? maxClosestRate : Fps();
 }
 
 FrameRateCompatibility LayerInfo::FrameRate::convertCompatibility(int8_t compatibility) {
@@ -466,6 +500,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:
@@ -496,6 +532,8 @@
             return FrameRateCategory::Low;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL:
             return FrameRateCategory::Normal;
+        case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH_HINT:
+            return FrameRateCategory::HighHint;
         case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH:
             return FrameRateCategory::High;
         default:
@@ -507,10 +545,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 7d3cffa..326e444 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
@@ -174,7 +175,8 @@
                             bool pendingModeChange, const LayerProps& props);
 
     // Sets an explicit layer vote. This usually comes directly from the application via
-    // ANativeWindow_setFrameRate API
+    // ANativeWindow_setFrameRate API. This is also used by Game Default Frame Rate and
+    // Game Mode Intervention Frame Rate.
     void setLayerVote(LayerVote vote) { mLayerVote = vote; }
 
     // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
@@ -182,6 +184,8 @@
     // layer can go back to whatever vote it had before the app voted for it.
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
+    void setProperties(const LayerProps&);
+
     // Resets the layer vote to its default.
     void resetLayerVote() {
         mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default, FrameRateCategory::Default};
@@ -200,6 +204,7 @@
     FrameRate getSetFrameRateVote() const;
     bool isVisible() const;
     int32_t getFrameRateSelectionPriority() const;
+    bool isFrontBuffered() const;
     FloatRect getBounds() const;
     ui::Transform getTransform() const;
 
@@ -261,8 +266,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;
@@ -282,13 +287,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
@@ -360,6 +366,7 @@
     LayerInfo::FrameRate setFrameRateVote;
     int32_t frameRateSelectionPriority = -1;
     bool isSmallDirty = false;
+    bool isFrontBuffered = false;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 18c0a69..cf8b3bf 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -125,7 +125,7 @@
         mVsync.scheduledFrameTime =
                 mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                                .readyDuration = 0,
-                                               .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                               .lastVsync = mVsync.lastCallbackTime.ns()});
     }
     return oldRegistration;
 }
@@ -143,7 +143,7 @@
     mVsync.scheduledFrameTime =
             mVsync.registration->update({.workDuration = mVsync.workDuration.get().count(),
                                          .readyDuration = 0,
-                                         .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                         .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
 void MessageQueue::waitMessage() {
@@ -196,7 +196,7 @@
     mVsync.scheduledFrameTime =
             mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                            .readyDuration = 0,
-                                           .earliestVsync = mVsync.lastCallbackTime.ns()});
+                                           .lastVsync = mVsync.lastCallbackTime.ns()});
 }
 
 auto MessageQueue::getScheduledFrameTime() const -> std::optional<Clock::time_point> {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 1d23fb5..c3709e5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -36,9 +36,7 @@
 #include <scheduler/FrameRateMode.h>
 #include <utils/Trace.h>
 
-#include "../SurfaceFlingerProperties.h"
 #include "RefreshRateSelector.h"
-#include "Utils/FlagUtils.h"
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
@@ -115,7 +113,7 @@
     using fps_approx_ops::operator/;
     // use signed type as `fps / range.max` might be 0
     auto start = std::max(1, static_cast<int>(peakFps / range.max) - 1);
-    if (flagutils::vrrConfigEnabled()) {
+    if (FlagManager::getInstance().vrr_config()) {
         start = std::max(1,
                          static_cast<int>(vsyncRate /
                                           std::min(range.max, peakFps, fps_approx_ops::operator<)) -
@@ -334,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<;
@@ -392,21 +399,32 @@
     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 {
-    ATRACE_CALL();
     // Slightly prefer seamless switches.
     constexpr float kSeamedSwitchPenalty = 0.95f;
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
 
     if (layer.vote == LayerVoteType::ExplicitCategory) {
+        // HighHint is considered later for touch boost.
+        if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+            return 0.f;
+        }
+
         if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) {
             return 1.f;
         }
@@ -424,7 +442,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) {
@@ -492,7 +510,9 @@
     int explicitDefaultVoteLayers = 0;
     int explicitExactOrMultipleVoteLayers = 0;
     int explicitExact = 0;
+    int explicitGteLayers = 0;
     int explicitCategoryVoteLayers = 0;
+    int interactiveLayers = 0;
     int seamedFocusedLayers = 0;
     int categorySmoothSwitchOnlyLayers = 0;
 
@@ -516,8 +536,17 @@
             case LayerVoteType::ExplicitExact:
                 explicitExact++;
                 break;
+            case LayerVoteType::ExplicitGte:
+                explicitGteLayers++;
+                break;
             case LayerVoteType::ExplicitCategory:
-                explicitCategoryVoteLayers++;
+                if (layer.frameRateCategory == FrameRateCategory::HighHint) {
+                    // HighHint does not count as an explicit signal from an app. It may be
+                    // be a touch signal.
+                    interactiveLayers++;
+                } else {
+                    explicitCategoryVoteLayers++;
+                }
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
                     // Count this layer for Min vote as well. The explicit vote avoids
                     // touch boost and idle for choosing a category, while Min vote is for correct
@@ -538,7 +567,7 @@
     }
 
     const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
-            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 ||
+            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 || explicitGteLayers > 0 ||
             explicitCategoryVoteLayers > 0;
 
     const Policy* policy = getCurrentPolicyLocked();
@@ -691,6 +720,7 @@
                     case LayerVoteType::Max:
                     case LayerVoteType::ExplicitDefault:
                     case LayerVoteType::ExplicitExact:
+                    case LayerVoteType::ExplicitGte:
                     case LayerVoteType::ExplicitCategory:
                         return false;
                 }
@@ -813,13 +843,14 @@
     const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
     using fps_approx_ops::operator<;
 
-    if (signals.touch && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
+    const bool hasInteraction = signals.touch || interactiveLayers > 0;
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
         touchBoostForExplicitExact &&
         scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
         ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
                               to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
+        return {touchRefreshRates, GlobalSignals{.touch = signals.touch}};
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
@@ -973,17 +1004,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()) {
@@ -1080,7 +1115,7 @@
             return;
         }
 
-        float score = calculateDistanceScoreFromMax(frameRateMode.fps);
+        float score = calculateDistanceScoreFromMaxLocked(frameRateMode.fps);
 
         if (ascending) {
             score = 1.0f / score;
@@ -1489,7 +1524,8 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 90_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 60_Hz};
+            return FpsRange{30_Hz, 30_Hz};
+        case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
             LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
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 5b36a5e..27ca17f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -45,13 +45,18 @@
 #include <memory>
 #include <numeric>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
+#include "RefreshRateStats.h"
+#include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
+#include "TimeStats/TimeStats.h"
 #include "VSyncTracker.h"
+#include "VsyncConfiguration.h"
 #include "VsyncController.h"
 #include "VsyncSchedule.h"
 
@@ -66,11 +71,16 @@
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features,
-                     sp<VsyncModulator> modulatorPtr)
-      : impl::MessageQueue(compositor),
+                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats,
+                     IVsyncTrackerCallback& vsyncTrackerCallback)
+      : android::impl::MessageQueue(compositor),
         mFeatures(features),
-        mVsyncModulator(std::move(modulatorPtr)),
-        mSchedulerCallback(callback) {}
+        mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)),
+        mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())),
+        mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate,
+                                                             hal::PowerMode::OFF)),
+        mSchedulerCallback(callback),
+        mVsyncTrackerCallback(vsyncTrackerCallback) {}
 
 Scheduler::~Scheduler() {
     // MessageQueue depends on VsyncSchedule, so first destroy it.
@@ -90,7 +100,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),
@@ -115,10 +130,10 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
-    auto schedulePtr = std::make_shared<VsyncSchedule>(displayId, mFeatures,
-                                                       [this](PhysicalDisplayId id, bool enable) {
-                                                           onHardwareVsyncRequest(id, enable);
-                                                       });
+    auto schedulePtr = std::make_shared<VsyncSchedule>(
+            selectorPtr->getActiveMode().modePtr, mFeatures,
+            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
+            mVsyncTrackerCallback);
 
     registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
 }
@@ -175,44 +190,54 @@
     const FrameTargeter::BeginFrameArgs beginFrameArgs =
             {.frameBeginTime = SchedulerClock::now(),
              .vsyncId = vsyncId,
-             // TODO(b/255601557): Calculate per display.
              .expectedVsyncTime = expectedVsyncTime,
-             .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration};
+             .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration,
+             .hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration};
 
-    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());
+        // TODO (b/256196556): Followers should use the next VSYNC after the frontrunner, not the
+        // pacesetter.
+        // Update expectedVsyncTime, which may have been adjusted by beginFrame.
+        expectedVsyncTime = pacesetterPtr->targeterPtr->target().expectedPresentTime();
 
-    for (const auto& [id, display] : mDisplays) {
-        if (id == pacesetterId) continue;
+        for (const auto& [id, display] : mDisplays) {
+            if (id == pacesetterPtr->displayId) continue;
 
-        FrameTargeter& targeter = *display.targeterPtr;
-        targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
-        targets.try_emplace(id, &targeter.target());
+            auto followerBeginFrameArgs = beginFrameArgs;
+            followerBeginFrameArgs.expectedVsyncTime =
+                    display.schedulePtr->vsyncDeadlineAfter(expectedVsyncTime);
+
+            FrameTargeter& targeter = *display.targeterPtr;
+            targeter.beginFrame(followerBeginFrameArgs, *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);
     }
 
-    if (flagutils::vrrConfigEnabled() &&
+    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)",
@@ -221,7 +246,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) {
@@ -287,9 +324,10 @@
                                               frametimeline::TokenManager* tokenManager,
                                               std::chrono::nanoseconds workDuration,
                                               std::chrono::nanoseconds readyDuration) {
-    auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
-                                                           getVsyncSchedule(), tokenManager, *this,
-                                                           workDuration, readyDuration);
+    auto eventThread =
+            std::make_unique<android::impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
+                                                         getVsyncSchedule(), tokenManager, *this,
+                                                         workDuration, readyDuration);
 
     auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
     handle = createConnection(std::move(eventThread));
@@ -392,6 +430,17 @@
     thread->onFrameRateOverridesChanged(displayId, std::move(overrides));
 }
 
+void Scheduler::onHdcpLevelsChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
+                                    int32_t connectedLevel, int32_t maxLevel) {
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->onHdcpLevelsChanged(displayId, connectedLevel, maxLevel);
+}
+
 void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
@@ -464,8 +513,23 @@
     thread->setDuration(workDuration, readyDuration);
 }
 
-void Scheduler::setVsyncConfigSet(const VsyncConfigSet& configs, Period vsyncPeriod) {
-    setVsyncConfig(mVsyncModulator->setVsyncConfigSet(configs), vsyncPeriod);
+void Scheduler::updatePhaseConfiguration(Fps refreshRate) {
+    mRefreshRateStats->setRefreshRate(refreshRate);
+    mVsyncConfiguration->setRefreshRateFps(refreshRate);
+    setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()),
+                   refreshRate.getPeriod());
+}
+
+void Scheduler::resetPhaseConfiguration(Fps refreshRate) {
+    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
+    mVsyncModulator->cancelRefreshRateChange();
+
+    mVsyncConfiguration->reset();
+    updatePhaseConfiguration(refreshRate);
+}
+
+void Scheduler::setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode powerMode) {
+    mRefreshRateStats->setPowerMode(powerMode);
 }
 
 void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) {
@@ -495,13 +559,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());
@@ -510,12 +577,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);
         }
     }
 }
@@ -526,7 +593,7 @@
 
     // On main thread to serialize reads/writes of pending hardware VSYNC state.
     static_cast<void>(
-            schedule([=]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
+            schedule([=, this]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
                 if (const auto displayOpt = mDisplays.get(id)) {
@@ -564,6 +631,26 @@
     display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
 
+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,
+                                                  currentExpectedPresentTime.ns());
+    return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
+}
+
 void Scheduler::resync() {
     static constexpr nsecs_t kIgnoreDelay = ms2ns(750);
 
@@ -589,6 +676,7 @@
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
+    ATRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
     const auto scheduleOpt =
             (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
                 return display.powerMode == hal::PowerMode::OFF
@@ -599,7 +687,8 @@
     if (!scheduleOpt) return;
     const auto& schedule = scheduleOpt->get();
 
-    if (const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence))) {
+    const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence));
+    if (needMoreSignals) {
         schedule->enableHardwareVsync();
     } else {
         constexpr bool kDisallow = false;
@@ -640,6 +729,10 @@
                                                    mFeatures.test(Feature::kContentDetection));
 }
 
+void Scheduler::setLayerProperties(int32_t id, const android::scheduler::LayerProps& properties) {
+    mLayerHistory.setLayerProperties(id, properties);
+}
+
 void Scheduler::chooseRefreshRateForContent(
         const surfaceflinger::frontend::LayerHierarchy* hierarchy,
         bool updateAttachedChoreographer) {
@@ -809,6 +902,12 @@
     mFrameRateOverrideMappings.dump(dumper);
     dumper.eol();
 
+    mVsyncConfiguration->dump(dumper.out());
+    dumper.eol();
+
+    mRefreshRateStats->dump(dumper.out());
+    dumper.eol();
+
     {
         utils::Dumper::Section section(dumper, "Frame Targeting"sv);
 
@@ -895,9 +994,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;
 }
@@ -1164,7 +1263,7 @@
     }
 }
 
-bool Scheduler::onPostComposition(nsecs_t presentTime) {
+bool Scheduler::onCompositionPresented(nsecs_t presentTime) {
     std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
     if (mLastVsyncPeriodChangeTimeline && mLastVsyncPeriodChangeTimeline->refreshRequired) {
         if (presentTime < mLastVsyncPeriodChangeTimeline->refreshTimeNanos) {
@@ -1181,12 +1280,27 @@
     mLayerHistory.setDisplayArea(displayArea);
 }
 
-void Scheduler::setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) {
+void Scheduler::setGameModeFrameRateForUid(FrameRateOverride frameRateOverride) {
     if (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f) {
         return;
     }
 
-    mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
+    if (FlagManager::getInstance().game_default_frame_rate()) {
+        // update the frame rate override mapping in LayerHistory
+        mLayerHistory.updateGameModeFrameRateOverride(frameRateOverride);
+    } else {
+        mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
+    }
+}
+
+void Scheduler::setGameDefaultFrameRateForUid(FrameRateOverride frameRateOverride) {
+    if (!FlagManager::getInstance().game_default_frame_rate() ||
+        (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f)) {
+        return;
+    }
+
+    // update the frame rate override mapping in LayerHistory
+    mLayerHistory.updateGameDefaultFrameRateOverride(frameRateOverride);
 }
 
 void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
@@ -1203,7 +1317,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 b0520a6..f62f1ba 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>
@@ -87,22 +88,30 @@
 namespace android {
 
 class FenceTime;
+class TimeStats;
 
 namespace frametimeline {
 class TokenManager;
 } // namespace frametimeline
 
+namespace surfaceflinger {
+class Factory;
+} // namespace surfaceflinger
+
 namespace scheduler {
 
 using GlobalSignals = RefreshRateSelector::GlobalSignals;
 
+class RefreshRateStats;
+class VsyncConfiguration;
 class VsyncSchedule;
 
 class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
 public:
-    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>);
+    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, surfaceflinger::Factory&,
+              Fps activeRefreshRate, TimeStats&, IVsyncTrackerCallback&);
     virtual ~Scheduler();
 
     void startTimers();
@@ -171,6 +180,8 @@
     void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
             EXCLUDES(mConnectionsLock);
 
+    void onHdcpLevelsChanged(ConnectionHandle, PhysicalDisplayId, int32_t, int32_t);
+
     // Modifies work duration in the event thread.
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
@@ -197,7 +208,10 @@
         }
     }
 
-    void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod);
+    void updatePhaseConfiguration(Fps);
+    void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext);
+
+    const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
 
     // Sets the render rate for the scheduler to run at.
     void setRenderRate(PhysicalDisplayId, Fps);
@@ -209,13 +223,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; }
 
@@ -233,6 +246,7 @@
                             nsecs_t now, LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
     void setDefaultFrameRateCompatibility(int32_t id, scheduler::FrameRateCompatibility);
+    void setLayerProperties(int32_t id, const LayerProps&);
     void deregisterLayer(Layer*);
     void onLayerDestroyed(Layer*) EXCLUDES(mChoreographerLock);
 
@@ -245,8 +259,10 @@
     // Indicates that touch interaction is taking place.
     void onTouchHint();
 
-    void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode)
-            REQUIRES(kMainThreadContext);
+    void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode) REQUIRES(kMainThreadContext);
+
+    // TODO(b/255635821): Track this per display.
+    void setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode) REQUIRES(kMainThreadContext);
 
     ConstVsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> = std::nullopt) const
             EXCLUDES(mDisplayLock);
@@ -281,8 +297,8 @@
     // Notifies the scheduler about a refresh rate timeline change.
     void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline);
 
-    // Notifies the scheduler post composition. Returns if recomposite is needed.
-    bool onPostComposition(nsecs_t presentTime);
+    // Notifies the scheduler once the composition is presented. Returns if recomposite is needed.
+    bool onCompositionPresented(nsecs_t presentTime);
 
     // Notifies the scheduler when the display size has changed. Called from SF's main thread
     void onActiveDisplayAreaChanged(uint32_t displayArea);
@@ -291,7 +307,17 @@
     // FrameRateOverride.refreshRateHz == 0 means no preference.
     void setPreferredRefreshRateForUid(FrameRateOverride);
 
-    void setGameModeRefreshRateForUid(FrameRateOverride);
+    // Stores the frame rate override that a game should run at set by game interventions.
+    // FrameRateOverride.refreshRateHz == 0 means no preference.
+    void setGameModeFrameRateForUid(FrameRateOverride) EXCLUDES(mDisplayLock);
+
+    // Stores the frame rate override that a game should run rat set by default game frame rate.
+    // FrameRateOverride.refreshRateHz == 0 means no preference, game default game frame rate is not
+    // enabled.
+    //
+    // "ro.surface_flinger.game_default_frame_rate_override" sets the frame rate value,
+    // "persist.graphics.game_default_frame_rate.enabled" controls whether this feature is enabled.
+    void setGameDefaultFrameRateForUid(FrameRateOverride) EXCLUDES(mDisplayLock);
 
     void updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
@@ -311,6 +337,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);
@@ -318,9 +347,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.
@@ -352,7 +382,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);
@@ -446,9 +476,14 @@
 
     const FeatureFlags mFeatures;
 
+    // Stores phase offsets configured per refresh rate.
+    const std::unique_ptr<VsyncConfiguration> mVsyncConfiguration;
+
     // Shifts the VSYNC phase during certain transactions and refresh rate changes.
     const sp<VsyncModulator> mVsyncModulator;
 
+    const std::unique_ptr<RefreshRateStats> mRefreshRateStats;
+
     // Used to choose refresh rate if content detection is enabled.
     LayerHistory mLayerHistory;
 
@@ -462,6 +497,8 @@
 
     ISchedulerCallback& mSchedulerCallback;
 
+    IVsyncTrackerCallback& mVsyncTrackerCallback;
+
     // mDisplayLock may be locked while under mPolicyLock.
     mutable std::mutex mPolicyLock;
 
@@ -477,9 +514,7 @@
               : displayId(displayId),
                 selectorPtr(std::move(selectorPtr)),
                 schedulePtr(std::move(schedulePtr)),
-                targeterPtr(std::make_unique<
-                            FrameTargeter>(displayId,
-                                           features.test(Feature::kBackpressureGpuComposition))) {}
+                targeterPtr(std::make_unique<FrameTargeter>(displayId, features)) {}
 
         const PhysicalDisplayId displayId;
 
@@ -513,13 +548,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/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index c3a952f..f978016 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -84,8 +84,8 @@
      *                 able to provide the ready-by time (deadline) on the callback.
      *                 For internal clients, we don't need to add additional padding, so
      *                 readyDuration will typically be 0.
-     * @earliestVsync: The targeted display time. This will be snapped to the closest
-     *                 predicted vsync time after earliestVsync.
+     * @lastVsync: The targeted display time. This will be snapped to the closest
+     *                 predicted vsync time after lastVsync.
      *
      * callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
@@ -93,11 +93,11 @@
     struct ScheduleTiming {
         nsecs_t workDuration = 0;
         nsecs_t readyDuration = 0;
-        nsecs_t earliestVsync = 0;
+        nsecs_t lastVsync = 0;
 
         bool operator==(const ScheduleTiming& other) const {
             return workDuration == other.workDuration && readyDuration == other.readyDuration &&
-                    earliestVsync == other.earliestVsync;
+                    lastVsync == other.lastVsync;
         }
 
         bool operator!=(const ScheduleTiming& other) const { return !(*this == other); }
@@ -109,12 +109,12 @@
      * The callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
      *
-     * The caller designates the earliest vsync event that should be targeted by the earliestVsync
+     * The caller designates the earliest vsync event that should be targeted by the lastVsync
      * parameter.
      * The callback will be scheduled at (workDuration + readyDuration - predictedVsync), where
-     * predictedVsync is the first vsync event time where ( predictedVsync >= earliestVsync ).
+     * predictedVsync is the first vsync event time where ( predictedVsync >= lastVsync ).
      *
-     * If (workDuration + readyDuration - earliestVsync) is in the past, or if a callback has
+     * If (workDuration + readyDuration - lastVsync) is in the past, or if a callback has
      * already been dispatched for the predictedVsync, an error will be returned.
      *
      * It is valid to reschedule a callback to a different time.
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index f467670..5cb0ffb 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -25,33 +25,32 @@
 
 #include <scheduler/TimeKeeper.h>
 
+#include <common/FlagManager.h>
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
-#include <com_android_graphics_surfaceflinger_flags.h>
-
 #undef LOG_TAG
 #define LOG_TAG "VSyncDispatch"
 
 namespace android::scheduler {
-using namespace com::android::graphics::surfaceflinger;
 
 using base::StringAppendF;
 
 namespace {
 
-nsecs_t getExpectedCallbackTime(nsecs_t now, nsecs_t nextVsyncTime,
+nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
                                 const VSyncDispatch::ScheduleTiming& timing) {
-    const auto expectedCallbackTime = nextVsyncTime - timing.readyDuration - timing.workDuration;
-    const auto baseTime = flags::dont_skip_on_early() ? now : expectedCallbackTime;
-    return std::max(baseTime, expectedCallbackTime);
+    return nextVsyncTime - timing.readyDuration - timing.workDuration;
 }
 
 nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
                                 const VSyncDispatch::ScheduleTiming& timing) {
-    const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
-    return getExpectedCallbackTime(now, nextVsyncTime, timing);
+    const auto nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
+                                                          now + timing.workDuration +
+                                                                  timing.readyDuration),
+                                                 timing.lastVsync);
+    return getExpectedCallbackTime(nextVsyncTime, timing);
 }
 
 } // namespace
@@ -97,31 +96,36 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
-            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    auto nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
+                                                          now + timing.workDuration +
+                                                                  timing.readyDuration),
+                                                 timing.lastVsync);
     auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 
     bool const wouldSkipAVsyncTarget =
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    if (flags::dont_skip_on_early()) {
+    if (FlagManager::getInstance().dont_skip_on_early()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
-            return getExpectedCallbackTime(now, mArmedInfo->mActualVsyncTime, timing);
+            nextVsyncTime = mArmedInfo->mActualVsyncTime;
+        } else {
+            nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
         }
+        nextWakeupTime = std::max(now, nextVsyncTime - timing.workDuration - timing.readyDuration);
     } else {
         if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
-            return getExpectedCallbackTime(now, nextVsyncTime, timing);
+            return getExpectedCallbackTime(nextVsyncTime, timing);
         }
+        nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
+        nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
     }
 
-    nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
-    nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
-
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
-    return getExpectedCallbackTime(now, nextVsyncTime, timing);
+    return nextWakeupTime;
 }
 
 void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
@@ -141,11 +145,13 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
+                                                    *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod);
+        return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
+                                                    *mLastDispatchTime + currentPeriod);
     }
 
     return nextVsyncTime;
@@ -162,11 +168,12 @@
     }
 
     const auto earliestReadyBy = now + mScheduleTiming.workDuration + mScheduleTiming.readyDuration;
-    const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.earliestVsync);
+    const auto earliestVsync = std::max(earliestReadyBy, mScheduleTiming.lastVsync);
 
     const auto nextVsyncTime =
             adjustVsyncIfNeeded(tracker, /*nextVsyncTime*/
-                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync));
+                                tracker.nextAnticipatedVSyncTimeFrom(earliestVsync,
+                                                                     mScheduleTiming.lastVsync));
     const auto nextReadyTime = nextVsyncTime - mScheduleTiming.readyDuration;
     const auto nextWakeupTime = nextReadyTime - mScheduleTiming.workDuration;
 
@@ -216,10 +223,10 @@
     StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(),
                   mRunning ? "(in callback function)" : "", armedInfo.c_str());
     StringAppendF(&result,
-                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms earliestVsync: %.2fms relative "
+                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms lastVsync: %.2fms relative "
                   "to now\n",
                   mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f,
-                  (mScheduleTiming.earliestVsync - systemTime()) / 1e6f);
+                  (mScheduleTiming.lastVsync - systemTime()) / 1e6f);
 
     if (mLastDispatchTime) {
         StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n",
@@ -239,6 +246,7 @@
 
 VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() {
     std::lock_guard lock(mMutex);
+    mRunning = false;
     cancelTimer();
     for (auto& [_, entry] : mCallbacks) {
         ALOGE("Forgot to unregister a callback on VSyncDispatch!");
@@ -307,6 +315,10 @@
     std::vector<Invocation> invocations;
     {
         std::lock_guard lock(mMutex);
+        if (!mRunning) {
+            ALOGD("TimerQueue is not running. Skipping callback.");
+            return;
+        }
         auto const now = mTimeKeeper->now();
         mLastTimerCallback = now;
         for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index e0fb8f9..3d08410 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -148,6 +148,10 @@
 
     std::mutex mutable mMutex;
 
+    // During VSyncDispatchTimerQueue deconstruction, skip timerCallback to
+    // avoid crash
+    bool mRunning = true;
+
     static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
     std::unique_ptr<TimeKeeper> const mTimeKeeper;
     VsyncSchedule::TrackerPtr mTracker;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e969fdc..28e35de 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -29,6 +29,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
@@ -46,14 +47,16 @@
 
 VSyncPredictor::~VSyncPredictor() = default;
 
-VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
-                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
-      : mId(id),
+VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+                               IVsyncTrackerCallback& callback)
+      : mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
-        mIdealPeriod(idealPeriod) {
+        mVsyncTrackerCallback(callback),
+        mDisplayModePtr(modePtr) {
     resetModel();
 }
 
@@ -71,15 +74,21 @@
     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)) {
+        ATRACE_FORMAT_INSTANT("timestamp is not aligned with model");
         return false;
     }
 
@@ -87,9 +96,10 @@
                                        [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
+        ATRACE_FORMAT_INSTANT("duplicate timestamp");
         return false;
     }
     return true;
@@ -97,10 +107,29 @@
 
 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) {
+    ATRACE_CALL();
+
     std::lock_guard lock(mMutex);
 
     if (!validate(timestamp)) {
@@ -119,6 +148,8 @@
         } else {
             mKnownTimestamp = timestamp;
         }
+        ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
+            (systemTime() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -134,7 +165,7 @@
 
     const size_t numSamples = mTimestamps.size();
     if (numSamples < kMinimumSamplesForPrediction) {
-        mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
+        mRateMap[idealPeriod()] = {idealPeriod(), 0};
         return true;
     }
 
@@ -158,7 +189,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
@@ -196,7 +227,7 @@
     }
 
     if (CC_UNLIKELY(bottom == 0)) {
-        it->second = {mIdealPeriod, 0};
+        it->second = {idealPeriod(), 0};
         clearTimestamps();
         return false;
     }
@@ -204,9 +235,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;
     }
@@ -222,7 +253,7 @@
 }
 
 auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
-    const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp);
+    const auto vsync = snapToVsync(timestamp);
     if (!mLastVsyncSequence) return {vsync, 0};
 
     const auto [slope, _] = getVSyncPredictionModelLocked();
@@ -232,14 +263,14 @@
     return {vsync, vsyncSequence};
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
     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());
@@ -268,23 +299,48 @@
     return prediction;
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                                     std::optional<nsecs_t> lastVsyncOpt) const {
+    ATRACE_CALL();
     std::lock_guard lock(mMutex);
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+    const auto minFramePeriod = minFramePeriodLocked().ns();
+    const auto lastFrameMissed =
+            lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold;
+    const nsecs_t baseTime =
+            FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
+            ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
+            : timePoint;
+    const auto vsyncTime = snapToVsyncAlignedWithRenderRate(baseTime);
+    if (FlagManager::getInstance().vrr_config()) {
+        const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
+        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+        mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
+    }
+    return vsyncTime;
+}
 
+nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
     // update the mLastVsyncSequence for reference point
     mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
 
     const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
-        if (!mRenderRate) return 0;
-
+        if (!mRenderRateOpt) return 0;
         const auto divisor =
-                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod),
-                                                         *mRenderRate);
+                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;
     }();
 
@@ -294,7 +350,7 @@
 
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
     const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
-    return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
+    return snapToVsync(approximateNextVsync - slope / 2);
 }
 
 /*
@@ -308,7 +364,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));
 }
 
@@ -324,7 +381,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,
@@ -332,10 +389,123 @@
     return vsyncSequence.seq % divisor == 0;
 }
 
-void VSyncPredictor::setRenderRate(Fps fps) {
-    ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str());
+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);
-    mRenderRate = fps;
+    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->timeoutNs).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();
+            }
+            mPastExpectedPresentTimes.clear();
+        }
+    }
+}
+
+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);
+    }
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+    mPastExpectedPresentTimes.push_back(expectedPresentTime);
+
+    while (!mPastExpectedPresentTimes.empty()) {
+        const auto front = mPastExpectedPresentTimes.front().ns();
+        const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
+        if (frontIsBeforeConfirmed) {
+            if (CC_UNLIKELY(mTraceOn)) {
+                ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+                                      static_cast<float>(lastConfirmedPresentTime.ns() - front) /
+                                              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);
+    mLastMissedVsync = expectedPresentTime;
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
@@ -345,28 +515,12 @@
 }
 
 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());
-    }
-
-    mIdealPeriod = period;
-    if (mRateMap.find(period) == mRateMap.end()) {
-        mRateMap[mIdealPeriod] = {period, 0};
-    }
-
-    clearTimestamps();
+    return mRateMap.find(idealPeriod())->second;
 }
 
 void VSyncPredictor::clearTimestamps() {
+    ATRACE_CALL();
+
     if (!mTimestamps.empty()) {
         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
         if (mKnownTimestamp) {
@@ -387,18 +541,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 c01c44d..9191003 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,30 +32,26 @@
 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,
-                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
+    VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+                   IVsyncTrackerCallback&);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
+                                         std::optional<nsecs_t> lastVsyncOpt = {}) 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.
      */
@@ -69,8 +66,14 @@
 
     bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const 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);
 
 private:
@@ -86,22 +89,26 @@
     size_t next(size_t i) const REQUIRES(mMutex);
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
     Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
-    nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
+    nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex);
+    nsecs_t snapToVsyncAlignedWithRenderRate(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;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
+    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
@@ -110,9 +117,14 @@
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
-    std::optional<Fps> mRenderRate 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);
+
+    TimePoint mLastMissedVsync GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 2938aa3..186a2d6 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -53,6 +53,8 @@
 VSyncReactor::~VSyncReactor() = default;
 
 bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
+    ATRACE_CALL();
+
     if (!fence) {
         return false;
     }
@@ -64,6 +66,8 @@
 
     std::lock_guard lock(mMutex);
     if (mExternalIgnoreFences || mInternalIgnoreFences) {
+        ATRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
+            mExternalIgnoreFences, mInternalIgnoreFences);
         return true;
     }
 
@@ -116,32 +120,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 +165,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 +193,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 +236,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 bc0e3bc..417163f 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -16,13 +16,22 @@
 
 #pragma once
 
+#include <ui/DisplayId.h>
 #include <utils/Timers.h>
 
 #include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
 
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
+
+struct IVsyncTrackerCallback {
+    virtual ~IVsyncTrackerCallback() = default;
+    virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                                  Fps renderRate) = 0;
+};
+
 /*
  * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
  * historical vsync timing data.
@@ -48,9 +57,13 @@
      * is updated.
      *
      * \param [in] timePoint    The point in time after which to estimate a vsync event.
+     * \param [in] lastVsyncOpt The last vsync time used by the client. If provided, the tracker
+     *                          should use that as a reference point when generating the new vsync
+     *                          and avoid crossing the minimal frame period of a VRR display.
      * \return                  A prediction of the timestamp of a vsync event.
      */
-    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0;
+    virtual nsecs_t nextAnticipatedVSyncTimeFrom(
+            nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt = {}) const = 0;
 
     /*
      * The current period of the vsync signal.
@@ -60,11 +73,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;
@@ -80,6 +91,15 @@
     virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
     /*
+     * 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
@@ -91,6 +111,11 @@
      */
     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;
 
 protected:
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 ff3f29d..db6a187 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -16,7 +16,10 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/FlagManager.h>
+
 #include <ftl/fake_guard.h>
+#include <gui/TraceUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
 
@@ -53,13 +56,14 @@
     VSyncCallbackRegistration mRegistration;
 };
 
-VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features,
-                             RequestHardwareVsync requestHardwareVsync)
-      : mId(id),
+VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
+                             RequestHardwareVsync requestHardwareVsync,
+                             IVsyncTrackerCallback& callback)
+      : mId(modePtr->getPhysicalDisplayId()),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(id)),
+        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) {}
@@ -78,6 +82,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()));
 }
@@ -100,15 +111,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);
+    return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
+                                            kDiscardOutlierPercent, callback);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
@@ -137,9 +148,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();
 }
 
@@ -169,6 +180,7 @@
 }
 
 void VsyncSchedule::enableHardwareVsyncLocked() {
+    ATRACE_CALL();
     if (mHwVsyncState == HwVsyncState::Disabled) {
         getTracker().resetModel();
         mRequestHardwareVsync(mId, true);
@@ -177,6 +189,7 @@
 }
 
 void VsyncSchedule::disableHardwareVsync(bool disallow) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
     switch (mHwVsyncState) {
         case HwVsyncState::Enabled:
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 47e92e1..722ea0b 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -31,6 +31,7 @@
 #include <scheduler/Time.h>
 
 #include "ThreadContext.h"
+#include "VSyncTracker.h"
 
 namespace android {
 class EventThreadTest;
@@ -56,21 +57,23 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(PhysicalDisplayId, FeatureFlags, RequestHardwareVsync);
+    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
@@ -124,7 +127,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(PhysicalDisplayId);
+    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/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index 7c72ac6..52485fb 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -29,6 +29,7 @@
     kTracePredictedVsync = 1 << 3,
     kBackpressureGpuComposition = 1 << 4,
     kSmallDirtyContentDetection = 1 << 5,
+    kExpectedPresentTime = 1 << 6,
 };
 
 using FeatureFlags = ftl::Flags<Feature>;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 19e951a..84ef89f 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -83,11 +83,12 @@
 };
 
 // The frame rate category of a Layer.
-enum class FrameRateCategory {
+enum class FrameRateCategory : int32_t {
     Default,
     NoPreference,
     Low,
     Normal,
+    HighHint,
     High,
 
     ftl_last = High
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index ae74205..a5bb6c2 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -19,11 +19,13 @@
 #include <array>
 #include <atomic>
 #include <memory>
+#include <optional>
 
 #include <ui/DisplayId.h>
 #include <ui/Fence.h>
 #include <ui/FenceTime.h>
 
+#include <scheduler/Features.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncId.h>
 #include <scheduler/interface/CompositeResult.h>
@@ -49,28 +51,23 @@
 
     TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
 
-    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
-    TimePoint pastVsyncTime(Period vsyncPeriod) const;
+    std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }
 
-    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
-    TimePoint previousFrameVsyncTime(Period vsyncPeriod) const {
-        return mExpectedPresentTime - vsyncPeriod;
-    }
+    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
+    TimePoint pastVsyncTime(Period minFramePeriod) const;
 
     // The present fence for the frame that had targeted the most recent VSYNC before this frame.
     // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
     // 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 isFramePending() const { return mFramePending; }
     bool didMissFrame() const { return mFrameMissed; }
     bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
@@ -79,9 +76,17 @@
     explicit FrameTarget(const std::string& displayLabel);
     ~FrameTarget() = default;
 
+    bool wouldPresentEarly(Period minFramePeriod) const;
+
+    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
+    TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
+        return mExpectedPresentTime - minFramePeriod;
+    }
+
     VsyncId mVsyncId;
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
+    std::optional<TimePoint> mEarliestPresentTime;
 
     TracedOrdinal<bool> mFramePending;
     TracedOrdinal<bool> mFrameMissed;
@@ -95,19 +100,22 @@
     std::array<FenceWithFenceTime, 2> mPresentFences;
 
 private:
+    friend class FrameTargeterTestBase;
+
     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;
     }
 };
 
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
 class FrameTargeter final : private FrameTarget {
 public:
-    FrameTargeter(PhysicalDisplayId displayId, bool backpressureGpuComposition)
+    FrameTargeter(PhysicalDisplayId displayId, FeatureFlags flags)
           : FrameTarget(to_string(displayId)),
-            mBackpressureGpuComposition(backpressureGpuComposition) {}
+            mBackpressureGpuComposition(flags.test(Feature::kBackpressureGpuComposition)),
+            mSupportsExpectedPresentTime(flags.test(Feature::kExpectedPresentTime)) {}
 
     const FrameTarget& target() const { return *this; }
 
@@ -116,10 +124,14 @@
         VsyncId vsyncId;
         TimePoint expectedVsyncTime;
         Duration sfWorkDuration;
+        Duration hwcMinWorkDuration;
     };
 
     void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
 
+    std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod,
+                                                        Duration hwcMinWorkDuration);
+
     // TODO(b/241285191): Merge with FrameTargeter::endFrame.
     FenceTimePtr setPresentFence(sp<Fence>);
 
@@ -128,7 +140,7 @@
     void dump(utils::Dumper&) const;
 
 private:
-    friend class FrameTargeterTest;
+    friend class FrameTargeterTestBase;
 
     // For tests.
     using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs);
@@ -138,6 +150,7 @@
     static bool isFencePending(const FenceTimePtr&, int graceTimeMs);
 
     const bool mBackpressureGpuComposition;
+    const bool mSupportsExpectedPresentTime;
 
     TimePoint mScheduledPresentTime;
     CompositionCoverageFlags mCompositionCoverage;
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..68c277d 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.
@@ -81,11 +82,15 @@
         }
     }
 
+    if (!mSupportsExpectedPresentTime) {
+        mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration);
+    }
+
     ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
                   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.
@@ -120,6 +125,14 @@
     if (mGpuFrameMissed) mGpuFrameMissedCount++;
 }
 
+std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period minFramePeriod,
+                                                                   Duration hwcMinWorkDuration) {
+    if (wouldPresentEarly(minFramePeriod)) {
+        return previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
+    }
+    return {};
+}
+
 void FrameTargeter::endFrame(const CompositeResult& result) {
     mCompositionCoverage = result.compositionCoverage;
 }
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 1e038d1..a9abcaf 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -17,48 +17,64 @@
 #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
 
-class FrameTargeterTest : public testing::Test {
+class FrameTargeterTestBase : public testing::Test {
 public:
+    FrameTargeterTestBase(FeatureFlags flags) : mTargeter(PhysicalDisplayId::fromPort(13), flags) {}
+
     const auto& target() const { return mTargeter.target(); }
 
+    bool wouldPresentEarly(Period minFramePeriod) const {
+        return target().wouldPresentEarly(minFramePeriod);
+    }
+
     struct Frame {
-        Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
-              Duration frameDuration, Fps refreshRate,
+        Frame(FrameTargeterTestBase* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
+              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 =
                                                              frameBeginTime + frameDuration,
-                                                     .sfWorkDuration = 10ms};
+                                                     .sfWorkDuration = 10ms,
+                                                     .hwcMinWorkDuration = kHwcMinWorkDuration};
 
             testPtr->mTargeter.beginFrame(args,
                                           vsyncSourceOpt
                                                   .or_else([&] {
                                                       return std::make_optional(
-                                                              VsyncSource(period,
+                                                              VsyncSource(period, period,
                                                                           args.expectedVsyncTime));
                                                   })
                                                   .value(),
@@ -84,26 +100,40 @@
         static bool fencePending(const FenceTimePtr&, int) { return true; }
         static bool fenceSignaled(const FenceTimePtr&, int) { return false; }
 
-        FrameTargeterTest* const testPtr;
+        FrameTargeterTestBase* const testPtr;
 
         TimePoint& frameBeginTime;
         const Period period;
+        const Period minFramePeriod;
 
         bool ended = false;
     };
 
+    static constexpr Duration kHwcMinWorkDuration = std::chrono::nanoseconds(5ns);
+
 private:
     FenceToFenceTimeMap mFenceMap;
 
-    static constexpr bool kBackpressureGpuComposition = true;
-    FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition};
+    FrameTargeter mTargeter;
+};
+
+class FrameTargeterTest : public FrameTargeterTestBase {
+public:
+    FrameTargeterTest() : FrameTargeterTestBase(Feature::kBackpressureGpuComposition) {}
+};
+
+class FrameTargeterWithExpectedPresentSupportTest : public FrameTargeterTestBase {
+public:
+    FrameTargeterWithExpectedPresentSupportTest()
+          : FrameTargeterTestBase(FeatureFlags(Feature::kBackpressureGpuComposition) |
+                                  Feature::kExpectedPresentTime) {}
 };
 
 TEST_F(FrameTargeterTest, targetsFrames) {
     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 +142,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 +157,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 +173,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 +191,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);
@@ -173,7 +228,7 @@
 TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
     constexpr Period kPeriod = (60_Hz).getPeriod();
     EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
-    EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod));
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresent) {
@@ -184,16 +239,51 @@
 
     // 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);
-        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // 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());
 
-    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, so it has an earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
+}
+
+// Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time
+// when there is expected present time support.
+TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) {
+    VsyncId vsyncId{333};
+    TimePoint frameBeginTime(3000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    // 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, kRefreshRate);
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    // The target is early if the past present fence was signaled.
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+    const auto fence = frame.end();
+    fence->signalForTest(frameBeginTime.ns());
+
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, but we have expected present time support, so it has no
+    // earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_EQ(std::nullopt, target().earliestPresentTime());
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
@@ -204,21 +294,28 @@
 
     // 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);
-        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
     }
 
-    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));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(target().earliestPresentTime());
 
-    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate); }
+    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
+
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
@@ -226,10 +323,13 @@
     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));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
 }
 
 TEST_F(FrameTargeterTest, detectsMissedFrames) {
@@ -243,7 +343,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 +351,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 +362,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 +371,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 +379,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 +391,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 fc51721..c3bfb58 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -103,6 +103,7 @@
 #include <cinttypes>
 #include <cmath>
 #include <cstdint>
+#include <filesystem>
 #include <functional>
 #include <memory>
 #include <mutex>
@@ -112,6 +113,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <common/FlagManager.h>
 #include <gui/LayerStatePermissions.h>
 #include <gui/SchedulingPolicy.h>
 #include <ui/DisplayIdentification.h>
@@ -128,7 +130,6 @@
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
@@ -156,15 +157,12 @@
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
 #include "Utils/Dumper.h"
-#include "Utils/FlagUtils.h"
 #include "WindowInfosListenerInvoker.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 
-#include <com_android_graphics_surfaceflinger_flags.h>
-
 #undef NO_THREAD_SAFETY_ANALYSIS
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
@@ -174,8 +172,6 @@
 #define DOES_CONTAIN_BORDER false
 
 namespace android {
-using namespace com::android::graphics::surfaceflinger;
-
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 using namespace std::string_view_literals;
@@ -309,6 +305,66 @@
     return LayerHandle::getLayerId(surfaceControl->getHandle());
 }
 
+/**
+ * Returns true if the file at path exists and is newer than duration.
+ */
+bool fileNewerThan(const std::string& path, std::chrono::minutes duration) {
+    using Clock = std::filesystem::file_time_type::clock;
+    std::error_code error;
+    std::filesystem::file_time_type updateTime = std::filesystem::last_write_time(path, error);
+    if (error) {
+        return false;
+    }
+    return duration > (Clock::now() - updateTime);
+}
+
+bool isFrameIntervalOnCadence(TimePoint expectedPresentTime, TimePoint lastExpectedPresentTimestamp,
+                              Fps lastFrameInterval, Period timeout, Duration threshold) {
+    if (lastFrameInterval.getPeriodNsecs() == 0) {
+        return false;
+    }
+
+    const auto expectedPresentTimeDeltaNs =
+            expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
+
+    if (expectedPresentTimeDeltaNs > timeout.ns()) {
+        return false;
+    }
+
+    const auto expectedPresentPeriods = static_cast<nsecs_t>(
+            std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
+                       static_cast<float>(lastFrameInterval.getPeriodNsecs())));
+    const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
+    const auto calculatedExpectedPresentTimeNs =
+            lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
+    const auto presentTimeDelta =
+            std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
+    return presentTimeDelta < threshold.ns();
+}
+
+bool isExpectedPresentWithinTimeout(TimePoint expectedPresentTime,
+                                    TimePoint lastExpectedPresentTimestamp,
+                                    std::optional<Period> timeoutOpt, Duration threshold) {
+    if (!timeoutOpt) {
+        // Always within timeout if timeoutOpt is absent and don't send hint
+        // for the timeout
+        return true;
+    }
+
+    if (timeoutOpt->ns() == 0) {
+        // Always outside timeout if timeoutOpt is 0 and always send
+        // the hint for the timeout.
+        return false;
+    }
+
+    if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
+        return true;
+    }
+
+    // Check if within the threshold as it can be just outside the timeout
+    return std::abs(expectedPresentTime.ns() -
+                    (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
+}
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -496,10 +552,11 @@
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
             base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
 
-    // Trunk-Stable flags
-    mMiscFlagValue = flags::misc1();
-    mConnectedDisplayFlagValue = flags::connected_display();
-    mMisc2FlagEarlyBootValue = flags::late_boot_misc2();
+    // These are set by the HWC implementation to indicate that they will use the workarounds.
+    mIsHotplugErrViaNegVsync =
+            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
+
+    mIsHdcpViaNegVsync = base::GetBoolProperty("debug.sf.hwc_hdcp_via_neg_vsync"s, false);
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -561,6 +618,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);
@@ -675,6 +735,7 @@
         return;
     }
     mBootFinished = true;
+    FlagManager::getMutableInstance().markBootCompleted();
     if (mStartPropertySetThread->join() != NO_ERROR) {
         ALOGE("Join StartPropertySetThread failed!");
     }
@@ -688,7 +749,7 @@
 
     mFrameTracer->initialize();
     mFrameTimeline->onBootFinished();
-    getRenderEngine().setEnableTracing(mFlagManager.use_skia_tracing());
+    getRenderEngine().setEnableTracing(FlagManager::getInstance().use_skia_tracing());
 
     // wait patiently for the window manager death
     const String16 name("window");
@@ -708,7 +769,7 @@
 
     sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
 
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (input == nullptr) {
             ALOGE("Failed to link to input service");
         } else {
@@ -716,10 +777,12 @@
         }
 
         readPersistentProperties();
-        mPowerAdvisor->onBootFinished();
-        const bool hintSessionEnabled = mFlagManager.use_adpf_cpu_hint();
+        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) {
@@ -729,7 +792,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");
             }
         }
@@ -741,10 +804,6 @@
             enableRefreshRateOverlay(true);
         }
     }));
-
-    LOG_ALWAYS_FATAL_IF(flags::misc1() != mMiscFlagValue, "misc1 flag is not boot stable!");
-
-    mMisc2FlagLateBootValue = flags::late_boot_misc2();
 }
 
 static std::optional<renderengine::RenderEngine::RenderEngineType>
@@ -874,7 +933,7 @@
         mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
 
         if (setSchedFifo(true) != NO_ERROR) {
-            ALOGW("Can't set SCHED_OTHER for primeCache");
+            ALOGW("Can't set SCHED_FIFO after primeCache");
         }
     }
 
@@ -899,7 +958,7 @@
     TransactionTraceWriter::getInstance().setWriterFunction(
             [&](const std::string& filename, bool overwrite) {
                 auto writeFn = [&]() {
-                    if (!overwrite && access(filename.c_str(), F_OK) == 0) {
+                    if (!overwrite && fileNewerThan(filename, std::chrono::minutes{10})) {
                         ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
                         return;
                     }
@@ -1049,7 +1108,7 @@
         outMode.peakRefreshRate = peakFps.getValue();
         outMode.vsyncRate = mode->getVsyncRate().getValue();
 
-        const auto vsyncConfigSet = mVsyncConfiguration->getConfigsForRefreshRate(
+        const auto vsyncConfigSet = mScheduler->getVsyncConfiguration().getConfigsForRefreshRate(
                 Fps::fromValue(outMode.peakRefreshRate));
         outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
         outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
@@ -1176,7 +1235,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());
 
@@ -1189,10 +1248,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);
 
@@ -1202,31 +1260,30 @@
             // 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.
             mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated);
 
             if (displayId == mActiveDisplayId) {
-                updatePhaseConfiguration(mode.fps);
+                mScheduler->updatePhaseConfiguration(mode.fps);
             }
 
             mScheduler->setModeChangePending(true);
             break;
-        case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch:
+        case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
             mScheduler->setRenderRate(displayId, mode.fps);
 
             if (displayId == mActiveDisplayId) {
-                updatePhaseConfiguration(mode.fps);
-                mRefreshRateStats->setRefreshRate(mode.fps);
+                mScheduler->updatePhaseConfiguration(mode.fps);
             }
 
             if (emitEvent) {
                 dispatchDisplayModeChangeEvent(displayId, mode);
             }
             break;
-        case DisplayDevice::DesiredActiveModeAction::None:
+        case DisplayDevice::DesiredModeAction::None:
             break;
     }
 }
@@ -1240,7 +1297,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto displayOpt =
                 FTL_FAKE_GUARD(mStateLock,
                                ftl::find_if(mPhysicalDisplays,
@@ -1286,60 +1343,61 @@
     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);
 
     if (displayId == mActiveDisplayId) {
-        mRefreshRateStats->setRefreshRate(activeMode.fps);
-        updatePhaseConfiguration(activeMode.fps);
+        mScheduler->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) {
-        updatePhaseConfiguration(renderFps);
+        mScheduler->updatePhaseConfiguration(renderFps);
     }
 }
 
@@ -1352,25 +1410,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;
         }
 
@@ -1378,19 +1434,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;
         }
 
@@ -1400,13 +1453,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;
         }
 
@@ -1427,16 +1474,16 @@
         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);
         }
     }
 }
 
 void SurfaceFlinger::disableExpensiveRendering() {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         ATRACE_NAME(whence);
         if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
@@ -1478,7 +1525,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto displayOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1559,7 +1606,7 @@
 status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& displayToken,
                                             DisplayModeId modeId) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto snapshotOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1587,7 +1634,7 @@
 
 status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().clearBootDisplayMode(*displayId);
         } else {
@@ -1626,7 +1673,7 @@
         ALOGE("hdrOutputConversion is not supported by this device.");
         return INVALID_OPERATION;
     }
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
         using AidlHdrConversionStrategy =
                 aidl::android::hardware::graphics::common::HdrConversionStrategy;
         using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
@@ -1686,7 +1733,7 @@
 
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             getHwComposer().setAutoLowLatencyMode(*displayId, on);
         } else {
@@ -1697,7 +1744,7 @@
 
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
             getHwComposer().setContentType(*displayId, type);
@@ -1751,7 +1798,7 @@
                                                           bool enable, uint8_t componentMask,
                                                           uint64_t maxFrames) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
                                                                     componentMask, maxFrames);
@@ -1804,7 +1851,7 @@
 
 status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
     outLayers->clear();
-    auto future = mScheduler->schedule([=] {
+    auto future = mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         mDrawingState.traverseInZOrder([&](Layer* layer) {
             outLayers->push_back(layer->getLayerDebugInfo(display.get()));
@@ -1910,7 +1957,7 @@
     }
 
     const char* const whence = __func__;
-    return ftl::Future(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
                    const bool supportsDisplayBrightnessCommand =
                            getHwComposer().getComposer()->isSupported(
@@ -2087,15 +2134,28 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    if (mConnectedDisplayFlagValue) {
+    if (FlagManager::getInstance().connected_display() && timestamp < 0 &&
+        vsyncPeriod.has_value()) {
         // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
-        if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
-            vsyncPeriod.value() == ~0) {
-            int hotplugErrorCode = static_cast<int32_t>(-timestamp);
-            ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+        if (mIsHotplugErrViaNegVsync && vsyncPeriod.value() == ~0) {
+            const int32_t hotplugErrorCode = static_cast<int32_t>(-timestamp);
+            ALOGD("SurfaceFlinger got hotplugErrorCode=%d for display %" PRIu64, hotplugErrorCode,
+                  hwcDisplayId);
             mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
             return;
         }
+
+        if (mIsHdcpViaNegVsync && vsyncPeriod.value() == ~1) {
+            const int32_t value = static_cast<int32_t>(-timestamp);
+            // one byte is good enough to encode android.hardware.drm.HdcpLevel
+            const int32_t maxLevel = (value >> 8) & 0xFF;
+            const int32_t connectedLevel = value & 0xFF;
+            ALOGD("SurfaceFlinger got HDCP level changed: connected=%d, max=%d for "
+                  "display=%" PRIu64,
+                  connectedLevel, maxLevel, hwcDisplayId);
+            updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel);
+            return;
+        }
     }
 
     ATRACE_NAME(vsyncPeriod
@@ -2111,15 +2171,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));
     }
 }
 
@@ -2151,19 +2224,16 @@
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
     ATRACE_CALL();
     if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
-        const Fps fps = Fps::fromPeriodNsecs(data.vsyncPeriodNanos);
-        ATRACE_FORMAT("%s Fps %d", __func__, fps.getIntValue());
-        static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
-            {
-                {
-                    const auto display = getDisplayDeviceLocked(*displayId);
-                    FTL_FAKE_GUARD(kMainThreadContext,
-                                   display->updateRefreshRateOverlayRate(fps,
-                                                                         display->getActiveMode()
-                                                                                 .fps,
-                                                                         /* setByHwc */ true));
-                }
-            }
+        const char* const whence = __func__;
+        static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+            const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
+                                                         ? data.refreshPeriodNanos
+                                                         : data.vsyncPeriodNanos);
+            ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
+            const auto display = getDisplayDeviceLocked(*displayId);
+            FTL_FAKE_GUARD(kMainThreadContext,
+                           display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
+                                                                 /* setByHwc */ true));
         }));
     }
 }
@@ -2213,12 +2283,13 @@
             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));
 
         const bool hasChanges =
-                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) ||
+                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation |
+                                      Changes::Geometry | Changes::Visibility) ||
                 (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
                         0;
 
@@ -2227,8 +2298,9 @@
         }
 
         auto it = mLegacyLayers.find(snapshot->sequence);
-        LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldn't find layer object for %s",
-                            snapshot->getDebugString().c_str());
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                        "Couldn't find layer object for %s",
+                                        snapshot->getDebugString().c_str());
 
         if (updateSmallDirty) {
             // Update small dirty flag while surface damage region or geometry changed
@@ -2246,8 +2318,13 @@
                 .setFrameRateVote = snapshot->frameRate,
                 .frameRateSelectionPriority = snapshot->frameRateSelectionPriority,
                 .isSmallDirty = snapshot->isSmallDirty,
+                .isFrontBuffered = snapshot->isFrontBuffered(),
         };
 
+        if (snapshot->changes.any(Changes::Geometry | Changes::Visibility)) {
+            mScheduler->setLayerProperties(snapshot->sequence, layerProps);
+        }
+
         if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
             mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
                                                          snapshot->defaultFrameRateCompatibility);
@@ -2312,11 +2389,7 @@
                 mLegacyLayers[layer->sequence] = layer;
             }
         }
-        if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) {
-            ATRACE_NAME("LayerHierarchyBuilder:update");
-            mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(),
-                                          mLayerLifecycleManager.getDestroyedLayers());
-        }
+        mLayerHierarchyBuilder.update(mLayerLifecycleManager);
     }
 
     bool mustComposite = false;
@@ -2376,8 +2449,9 @@
             const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
 
             auto it = mLegacyLayers.find(layer->id);
-            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                                layer->getDebugString().c_str());
+            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                            "Couldnt find layer object for %s",
+                                            layer->getDebugString().c_str());
             if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
                 if (!it->second->hasBuffer()) {
                     // The last latch time is used to classify a missed frame as buffer stuffing
@@ -2480,6 +2554,10 @@
 
     if (pacesetterFrameTarget.isFramePending()) {
         if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
+            if (FlagManager::getInstance().vrr_config()) {
+                mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
+                        pacesetterFrameTarget.expectedPresentTime());
+            }
             scheduleCommit(FrameHint::kNone);
             return false;
         }
@@ -2603,6 +2681,8 @@
         if (const auto display = getCompositionDisplayLocked(id)) {
             refreshArgs.outputs.push_back(display);
         }
+
+        refreshArgs.frameTargets.try_emplace(id, &targeter->target());
     }
 
     std::vector<DisplayId> displayIds;
@@ -2671,22 +2751,23 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
-
-    if (!getHwComposer().getComposer()->isSupported(
-                Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-        pacesetterTarget.wouldPresentEarly(vsyncPeriod)) {
-        const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
-
-        // TODO(b/255601557): Calculate and pass per-display values for each FrameTarget.
-        refreshArgs.earliestPresentTime =
-                pacesetterTarget.previousFrameVsyncTime(vsyncPeriod) - 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.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
-
+    {
+        auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
+        auto lastExpectedPresentTimestamp = TimePoint::fromNs(
+                notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
+        if (expectedPresentTime > lastExpectedPresentTimestamp) {
+            // If the values are not same, then hint is sent with newer value.
+            // And because composition always follows the notifyExpectedPresentIfRequired, we can
+            // skip updating the lastExpectedPresentTimestamp in this case.
+            notifyExpectedPresentData.lastExpectedPresentTimestamp
+                    .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
+        }
+    }
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
@@ -2746,11 +2827,11 @@
         mPowerAdvisor->reportActualWorkDuration();
     }
 
-    if (mScheduler->onPostComposition(presentTime)) {
+    if (mScheduler->onCompositionPresented(presentTime)) {
         scheduleComposite(FrameHint::kNone);
     }
 
-    postComposition(pacesetterId, frameTargeters, presentTime);
+    onCompositionPresented(pacesetterId, frameTargeters, presentTime);
 
     const bool hadGpuComposited =
             multiDisplayUnion(mCompositionCoverage).test(CompositionCoverage::Gpu);
@@ -2907,11 +2988,10 @@
     return ui::ROTATION_0;
 }
 
-void SurfaceFlinger::postComposition(PhysicalDisplayId pacesetterId,
-                                     const scheduler::FrameTargeters& frameTargeters,
-                                     nsecs_t presentStartTime) {
+void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId,
+                                            const scheduler::FrameTargeters& frameTargeters,
+                                            nsecs_t presentStartTime) {
     ATRACE_CALL();
-    ALOGV(__func__);
 
     ui::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<FenceTime>> presentFences;
     ui::PhysicalDisplayMap<PhysicalDisplayId, const sp<Fence>> gpuCompositionDoneFences;
@@ -2971,7 +3051,8 @@
     const auto schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
     const Period vsyncPeriod = schedule->period();
-    const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset;
+    const nsecs_t vsyncPhase =
+            mScheduler->getVsyncConfiguration().getCurrentConfigs().late.sfOffset;
 
     const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
                                             presentLatency.ns());
@@ -3003,8 +3084,9 @@
     mLayersWithBuffersRemoved.clear();
 
     for (const auto& layer: mLayersWithQueuedFrames) {
-        layer->onPostComposition(pacesetterDisplay.get(), pacesetterGpuCompositionDoneFenceTime,
-                                 pacesetterPresentFenceTime, compositorTiming);
+        layer->onCompositionPresented(pacesetterDisplay.get(),
+                                      pacesetterGpuCompositionDoneFenceTime,
+                                      pacesetterPresentFenceTime, compositorTiming);
         layer->releasePendingBuffer(presentTime.ns());
     }
 
@@ -3069,9 +3151,9 @@
                         [&, compositionDisplay = compositionDisplay](
                                 std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
                             auto it = mLegacyLayers.find(snapshot->sequence);
-                            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
-                                                "Couldnt find layer object for %s",
-                                                snapshot->getDebugString().c_str());
+                            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                            "Couldnt find layer object for %s",
+                                                            snapshot->getDebugString().c_str());
                             auto& legacyLayer = it->second;
                             sp<LayerFE> layerFe =
                                     legacyLayer->getCompositionEngineLayerFE(snapshot->path);
@@ -3353,9 +3435,10 @@
     }
 
     const sp<IBinder> token = sp<BBinder>::make();
+    const ui::DisplayConnectionType connectionType =
+            getHwComposer().getDisplayConnectionType(displayId);
 
-    mPhysicalDisplays.try_emplace(displayId, token, displayId,
-                                  getHwComposer().getDisplayConnectionType(displayId),
+    mPhysicalDisplays.try_emplace(displayId, token, displayId, connectionType,
                                   std::move(displayModes), std::move(colorModes),
                                   std::move(info.deviceProductInfo));
 
@@ -3363,7 +3446,8 @@
     state.physical = {.id = displayId,
                       .hwcDisplayId = hwcDisplayId,
                       .activeMode = std::move(activeMode)};
-    state.isSecure = true; // All physical displays are currently considered secure.
+    state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
+    state.isProtected = true;
     state.displayName = std::move(info.name);
 
     mCurrentState.displays.add(token, state);
@@ -3395,6 +3479,7 @@
                                            displayToken, compositionDisplay);
     creationArgs.sequenceId = state.sequenceId;
     creationArgs.isSecure = state.isSecure;
+    creationArgs.isProtected = state.isProtected;
     creationArgs.displaySurface = displaySurface;
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
@@ -3526,6 +3611,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());
@@ -3643,7 +3729,7 @@
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                resetPhaseConfiguration(display->getActiveMode().fps);
+                mScheduler->resetPhaseConfiguration(display->getActiveMode().fps);
             }
         }
         return;
@@ -3679,15 +3765,6 @@
     }
 }
 
-void SurfaceFlinger::resetPhaseConfiguration(Fps refreshRate) {
-    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
-    mScheduler->vsyncModulator().cancelRefreshRateChange();
-
-    mVsyncConfiguration->reset();
-    updatePhaseConfiguration(refreshRate);
-    mRefreshRateStats->setRefreshRate(refreshRate);
-}
-
 void SurfaceFlinger::processDisplayChangesLocked() {
     // here we take advantage of Vector's copy-on-write semantics to
     // improve performance by skipping the transaction entirely when
@@ -3798,7 +3875,6 @@
                 // first frame before the display is available, we rely
                 // on WMS and DMS to provide the right information
                 // so the client can calculate the hint.
-                ALOGV("Skipping reporting transform hint update for %s", layer->getDebugName());
                 layer->skipReportingTransformHint();
             } else {
                 layer->updateTransformHint(hintDisplay->getTransformHint());
@@ -4006,7 +4082,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());
@@ -4035,6 +4111,69 @@
     }
 }
 
+void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
+                                      ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+    const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
+    const auto timeoutOpt = [&]() -> 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->timeoutNs);
+    }();
+
+    notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
+                                    expectedPresentTime, renderRate, timeoutOpt);
+}
+
+void SurfaceFlinger::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
+                                                     Period vsyncPeriod,
+                                                     TimePoint expectedPresentTime,
+                                                     Fps frameInterval,
+                                                     std::optional<Period> timeoutOpt) {
+    {
+        auto& data = mNotifyExpectedPresentMap[displayId];
+        const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
+        const auto lastFrameInterval = data.lastFrameInterval;
+        data.lastFrameInterval = frameInterval;
+        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+
+        const constexpr nsecs_t kOneSecondNs =
+                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+        const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
+                                                                               : kOneSecondNs);
+        const bool frameIntervalIsOnCadence =
+                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+                                         lastFrameInterval, timeout, threshold);
+
+        const bool expectedPresentWithinTimeout =
+                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                               timeoutOpt, threshold);
+
+        using fps_approx_ops::operator!=;
+        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
+            data.lastExpectedPresentTimestamp = expectedPresentTime;
+        }
+
+        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+            return;
+        }
+        data.lastExpectedPresentTimestamp = expectedPresentTime;
+    }
+
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+        const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
+                                                                  frameInterval);
+        if (status != NO_ERROR) {
+            ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
+                  displayId.value);
+        }
+    }));
+}
+
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
     using namespace scheduler;
 
@@ -4042,16 +4181,15 @@
 
     const auto activeMode = display->refreshRateSelector().getActiveMode();
     const Fps activeRefreshRate = activeMode.fps;
-    mRefreshRateStats =
-            std::make_unique<RefreshRateStats>(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF);
-
-    mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate);
 
     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 (flags::enable_small_area_detection()) {
+        if (FlagManager::getInstance().enable_small_area_detection()) {
             features |= Feature::kSmallDirtyContentDetection;
         }
     }
@@ -4068,16 +4206,22 @@
     if (mBackpressureGpuComposition) {
         features |= Feature::kBackpressureGpuComposition;
     }
-
-    auto modulatorPtr = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
+    if (getHwComposer().getComposer()->isSupported(
+                Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) {
+        features |= Feature::kExpectedPresentTime;
+    }
 
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
-                                             std::move(modulatorPtr));
+                                             getFactory(), activeRefreshRate, *mTimeStats,
+                                             static_cast<IVsyncTrackerCallback&>(*this));
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    if (FlagManager::getInstance().vrr_config()) {
+        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
+    }
     mScheduler->startTimers();
 
-    const auto configs = mVsyncConfiguration->getCurrentConfigs();
+    const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs();
 
     mAppConnectionHandle =
             mScheduler->createEventThread(Scheduler::Cycle::Render,
@@ -4097,15 +4241,6 @@
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
-
-    mIsHotplugErrViaNegVsync =
-            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
-}
-
-void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
-    mVsyncConfiguration->setRefreshRateFps(refreshRate);
-    mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(),
-                                  refreshRate.getPeriod());
 }
 
 void SurfaceFlinger::doCommitTransactions() {
@@ -4287,7 +4422,7 @@
     if (mNumLayers >= MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
-        static_cast<void>(mScheduler->schedule([=] {
+        static_cast<void>(mScheduler->schedule([=, this] {
             ALOGE("Dumping layer keeping > 20 children alive:");
             bool leakingParentLayerFound = false;
             mDrawingState.traverse([&](Layer* layer) {
@@ -4308,7 +4443,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" : ""));
                         }
                     });
                 }
@@ -4677,7 +4813,7 @@
         return false;
     }
 
-    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2;
+    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->minFramePeriod() / 2;
 
     return predictedPresentTime >= expectedPresentTime &&
             predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
@@ -4879,6 +5015,7 @@
                         .transform = layer->getTransform(),
                         .setFrameRateVote = layer->getFrameRateForLayerTree(),
                         .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+                        .isFrontBuffered = layer->isFrontBuffered(),
                 };
                 layer->recordLayerHistoryAnimationTx(layerProps, now);
             }
@@ -5745,7 +5882,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
 
@@ -5774,22 +5911,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);
@@ -5803,14 +5943,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);
 
@@ -5819,17 +5969,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);
@@ -5840,7 +5997,7 @@
 
     if (displayId == mActiveDisplayId) {
         mTimeStats->setPowerMode(mode);
-        mRefreshRateStats->setPowerMode(mode);
+        mScheduler->setActiveDisplayPowerModeForRefreshRateStats(mode);
     }
 
     mScheduler->setDisplayPowerMode(displayId, mode);
@@ -5849,7 +6006,7 @@
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                kMainThreadContext) {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
@@ -5876,138 +6033,60 @@
             !PermissionCache::checkPermission(sDump, pid, uid)) {
         StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n",
                       pid, uid);
-    } else {
-        Dumper hwclayersDump = [this](const DumpArgs&, bool, std::string& result)
-                                       FTL_FAKE_GUARD(mStateLock) -> void const {
-            if (mLayerLifecycleManagerEnabled) {
-                mScheduler
-                        ->schedule([this, &result]() FTL_FAKE_GUARD(kMainThreadContext)
-                                           FTL_FAKE_GUARD(mStateLock) {
-                                               dumpHwcLayersMinidump(result);
-                                           })
-                        .get();
-            } else {
-                dumpHwcLayersMinidumpLockedLegacy(result);
-            }
-        };
-
-        static const std::unordered_map<std::string, Dumper> dumpers = {
-                {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
-                {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
-                {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
-                {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
-                {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
-                {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
-                {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
-                {"--hwclayers"s, std::move(hwclayersDump)},
-                {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
-                {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
-                {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
-                {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
-                {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
-                {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
-                {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
-                {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
-                {"--frontend"s, dumper(&SurfaceFlinger::dumpFrontEnd)},
-        };
-
-        const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
-
-        // Traversal of drawing state must happen on the main thread.
-        // Otherwise, SortedVector may have shared ownership during concurrent
-        // traversals, which can result in use-after-frees.
-        std::string compositionLayers;
-        mScheduler
-                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                    if (!mLayerLifecycleManagerEnabled) {
-                        StringAppendF(&compositionLayers, "Composition layers\n");
-                        mDrawingState.traverseInZOrder([&](Layer* layer) {
-                            auto* compositionState = layer->getCompositionState();
-                            if (!compositionState || !compositionState->isVisible) return;
-                            android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n",
-                                                         layer,
-                                                         layer->getDebugName()
-                                                                 ? layer->getDebugName()
-                                                                 : "<unknown>");
-                            compositionState->dump(compositionLayers);
-                        });
-                    } else {
-                        std::ostringstream out;
-                        out << "\nComposition list\n";
-                        ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-                        mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
-                                    if (snapshot->hasSomethingToDraw()) {
-                                        if (lastPrintedLayerStackHeader !=
-                                            snapshot->outputFilter.layerStack) {
-                                            lastPrintedLayerStackHeader =
-                                                    snapshot->outputFilter.layerStack;
-                                            out << "LayerStack=" << lastPrintedLayerStackHeader.id
-                                                << "\n";
-                                        }
-                                        out << "  " << *snapshot << "\n";
-                                    }
-                                });
-
-                        out << "\nInput list\n";
-                        lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-                        mLayerSnapshotBuilder.forEachInputSnapshot(
-                                [&](const frontend::LayerSnapshot& snapshot) {
-                                    if (lastPrintedLayerStackHeader !=
-                                        snapshot.outputFilter.layerStack) {
-                                        lastPrintedLayerStackHeader =
-                                                snapshot.outputFilter.layerStack;
-                                        out << "LayerStack=" << lastPrintedLayerStackHeader.id
-                                            << "\n";
-                                    }
-                                    out << "  " << snapshot << "\n";
-                                });
-
-                        out << "\nLayer Hierarchy\n"
-                            << mLayerHierarchyBuilder.getHierarchy() << "\n\n";
-                        compositionLayers = out.str();
-                        dumpHwcLayersMinidump(compositionLayers);
-                    }
-                })
-                .get();
-
-        bool dumpLayers = true;
-        {
-            TimedLock lock(mStateLock, s2ns(1), __func__);
-            if (!lock.locked()) {
-                StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
-                              strerror(-lock.status), lock.status);
-            }
-
-            if (const auto it = dumpers.find(flag); it != dumpers.end()) {
-                (it->second)(args, asProto, result);
-                dumpLayers = false;
-            } else if (!asProto) {
-                dumpAllLocked(args, compositionLayers, result);
-            }
-        }
-
-        if (dumpLayers) {
-            perfetto::protos::LayersTraceFileProto traceFileProto =
-                    mLayerTracing.createTraceFileProto();
-            perfetto::protos::LayersSnapshotProto* layersTrace = traceFileProto.add_entry();
-            perfetto::protos::LayersProto layersProto = dumpProtoFromMainThread();
-            layersTrace->mutable_layers()->Swap(&layersProto);
-            auto displayProtos = dumpDisplayProto();
-            layersTrace->mutable_displays()->Swap(&displayProtos);
-
-            if (asProto) {
-                result.append(traceFileProto.SerializeAsString());
-            } else {
-                // Dump info that we need to access from the main thread
-                const auto layerTree = LayerProtoParser::generateLayerTree(layersTrace->layers());
-                result.append(LayerProtoParser::layerTreeToString(layerTree));
-                result.append("\n");
-                dumpOffscreenLayers(result);
-            }
-        }
+        write(fd, result.c_str(), result.size());
+        return NO_ERROR;
     }
 
+    if (asProto && args.empty()) {
+        perfetto::protos::LayersTraceFileProto traceFileProto =
+                mLayerTracing.createTraceFileProto();
+        perfetto::protos::LayersSnapshotProto* layersTrace = traceFileProto.add_entry();
+        perfetto::protos::LayersProto layersProto = dumpProtoFromMainThread();
+        layersTrace->mutable_layers()->Swap(&layersProto);
+        auto displayProtos = dumpDisplayProto();
+        layersTrace->mutable_displays()->Swap(&displayProtos);
+        result.append(traceFileProto.SerializeAsString());
+        write(fd, result.c_str(), result.size());
+        return NO_ERROR;
+    }
+
+    static const std::unordered_map<std::string, Dumper> dumpers = {
+            {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
+            {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
+            {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
+            {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
+            {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
+            {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
+            {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
+            {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
+            {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
+            {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
+            {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
+            {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+            {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
+            {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
+            {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
+            {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
+            {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
+    };
+
+    const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
+    if (const auto it = dumpers.find(flag); it != dumpers.end()) {
+        (it->second)(args, asProto, result);
+        write(fd, result.c_str(), result.size());
+        return NO_ERROR;
+    }
+
+    // Traversal of drawing state must happen on the main thread.
+    // Otherwise, SortedVector may have shared ownership during concurrent
+    // traversals, which can result in use-after-frees.
+    std::string compositionLayers;
+    mScheduler
+            ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                dumpVisibleFrontEnd(compositionLayers);
+            })
+            .get();
+    dumpAll(args, compositionLayers, result);
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
 }
@@ -6083,10 +6162,6 @@
     dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
     dumper.eol();
 
-    mRefreshRateStats->dump(result);
-    dumper.eol();
-
-    mVsyncConfiguration->dump(result);
     StringAppendF(&result,
                   "         present offset: %9" PRId64 " ns\t        VSYNC period: %9" PRId64
                   " ns\n\n",
@@ -6219,35 +6294,81 @@
 }
 
 void SurfaceFlinger::dumpFrontEnd(std::string& result) {
-    mScheduler
-            ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                std::ostringstream out;
-                out << "\nComposition list\n";
-                ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-                for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
-                    if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
-                        lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
-                        out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+    std::ostringstream out;
+    out << "\nComposition list\n";
+    ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+    for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+        if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+            lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+            out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+        }
+        out << "  " << *snapshot << "\n";
+    }
+
+    out << "\nInput list\n";
+    lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+    mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+            lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+            out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+        }
+        out << "  " << snapshot << "\n";
+    });
+
+    out << "\nLayer Hierarchy\n"
+        << mLayerHierarchyBuilder.getHierarchy().dump() << "\nOffscreen Hierarchy\n"
+        << mLayerHierarchyBuilder.getOffscreenHierarchy().dump() << "\n\n";
+    result.append(out.str());
+}
+
+void SurfaceFlinger::dumpVisibleFrontEnd(std::string& result) {
+    if (!mLayerLifecycleManagerEnabled) {
+        StringAppendF(&result, "Composition layers\n");
+        mDrawingState.traverseInZOrder([&](Layer* layer) {
+            auto* compositionState = layer->getCompositionState();
+            if (!compositionState || !compositionState->isVisible) return;
+            android::base::StringAppendF(&result, "* Layer %p (%s)\n", layer,
+                                         layer->getDebugName() ? layer->getDebugName()
+                                                               : "<unknown>");
+            compositionState->dump(result);
+        });
+
+        StringAppendF(&result, "Offscreen Layers\n");
+        for (Layer* offscreenLayer : mOffscreenLayers) {
+            offscreenLayer->traverse(LayerVector::StateSet::Drawing,
+                                     [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
+        }
+    } else {
+        std::ostringstream out;
+        out << "\nComposition list\n";
+        ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                    if (snapshot->hasSomethingToDraw()) {
+                        if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+                            lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+                            out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+                        }
+                        out << "  " << *snapshot << "\n";
                     }
-                    out << "  " << *snapshot << "\n";
-                }
+                });
 
-                out << "\nInput list\n";
-                lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-                mLayerSnapshotBuilder.forEachInputSnapshot(
-                        [&](const frontend::LayerSnapshot& snapshot) {
-                            if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
-                                lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
-                                out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
-                            }
-                            out << "  " << snapshot << "\n";
-                        });
+        out << "\nInput list\n";
+        lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+        mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+            if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+                lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+                out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+            }
+            out << "  " << snapshot << "\n";
+        });
 
-                out << "\nLayer Hierarchy\n"
-                    << mLayerHierarchyBuilder.getHierarchy().dump() << "\n\n";
-                result.append(out.str());
-            })
-            .get();
+        out << "\nLayer Hierarchy\n"
+            << mLayerHierarchyBuilder.getHierarchy() << "\nOffscreen Hierarchy\n"
+            << mLayerHierarchyBuilder.getOffscreenHierarchy() << "\n\n";
+        result = out.str();
+        dumpHwcLayersMinidump(result);
+    }
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
@@ -6330,7 +6451,7 @@
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
+    return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get();
 }
 
 void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
@@ -6348,7 +6469,7 @@
 }
 
 void SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy(std::string& result) const {
-    for (const auto& [token, display] : mDisplays) {
+    for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         const auto displayId = HalDisplayId::tryCast(display->getId());
         if (!displayId) {
             continue;
@@ -6365,7 +6486,10 @@
 }
 
 void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const {
-    for (const auto& [token, display] : mDisplays) {
+    if (!mLayerLifecycleManagerEnabled) {
+        return dumpHwcLayersMinidumpLockedLegacy(result);
+    }
+    for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         const auto displayId = HalDisplayId::tryCast(display->getId());
         if (!displayId) {
             continue;
@@ -6382,16 +6506,23 @@
                 return;
             }
             auto it = mLegacyLayers.find(snapshot.sequence);
-            LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s",
-                                snapshot.getDebugString().c_str());
+            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                            "Couldnt find layer object for %s",
+                                            snapshot.getDebugString().c_str());
             it->second->miniDump(result, snapshot, ref);
         });
         result.append("\n");
     }
 }
 
-void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
-                                   std::string& result) const {
+void SurfaceFlinger::dumpAll(const DumpArgs& args, const std::string& compositionLayers,
+                             std::string& result) const {
+    TimedLock lock(mStateLock, s2ns(1), __func__);
+    if (!lock.locked()) {
+        StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
+                      strerror(-lock.status), lock.status);
+    }
+
     const bool colorize = !args.empty() && args[0] == String16("--color");
     Colorizer colorizer(colorize);
 
@@ -6461,17 +6592,6 @@
     result.append("SurfaceFlinger global state:\n");
     colorizer.reset(result);
 
-    StringAppendF(&result, "MiscFlagValue: %s\n", mMiscFlagValue ? "true" : "false");
-    StringAppendF(&result, "ConnectedDisplayFlagValue: %s\n",
-                  mConnectedDisplayFlagValue ? "true" : "false");
-    StringAppendF(&result, "Misc2FlagValue: %s (%s after boot)\n",
-                  mMisc2FlagLateBootValue ? "true" : "false",
-                  mMisc2FlagEarlyBootValue == mMisc2FlagLateBootValue ? "stable" : "modified");
-    StringAppendF(&result, "VrrConfigFlagValue: %s\n",
-                  flagutils::vrrConfigEnabled() ? "true" : "false");
-    StringAppendF(&result, "DontSkipOnEarlyFlagValue: %s\n",
-                  flags::dont_skip_on_early() ? "true" : "false");
-
     getRenderEngine().dump(result);
 
     result.append("ClientCache state:\n");
@@ -6548,7 +6668,7 @@
     /*
      * Dump flag/property manager state
      */
-    mFlagManager.dump(result);
+    FlagManager::getInstance().dump(result);
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
@@ -6746,8 +6866,7 @@
             case 1007: // Unused.
                 return NAME_NOT_FOUND;
             case 1008: // Toggle forced GPU composition.
-                mDebugDisableHWC = data.readInt32() != 0;
-                scheduleRepaint();
+                sfdo_forceClientComposition(data.readInt32() != 0);
                 return NO_ERROR;
             case 1009: // Toggle use of transform hint.
                 mDebugDisableTransformHint = data.readInt32() != 0;
@@ -7034,7 +7153,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
@@ -7213,7 +7332,7 @@
             // Second argument is a delay in ms for triggering the jank. This is useful for working
             // with tools that steal the adb connection. This argument is optional.
             case 1045: {
-                if (flagutils::vrrConfigEnabled()) {
+                if (FlagManager::getInstance().vrr_config()) {
                     float jankAmount = data.readFloat();
                     int32_t jankDelayMs = 0;
                     if (data.readInt32(&jankDelayMs) != NO_ERROR) {
@@ -7253,7 +7372,7 @@
 
     // Update the overlay on the main thread to avoid race conditions with
     // RefreshRateSelector::getActiveMode
-    static_cast<void>(mScheduler->schedule([=] {
+    static_cast<void>(mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
             ALOGW("%s: default display is null", __func__);
@@ -7261,15 +7380,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();
         }
     }));
@@ -7504,6 +7622,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);
@@ -7537,8 +7661,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;
@@ -7557,7 +7680,7 @@
                         args.allowProtected, args.grayscale, captureListener);
 }
 
-void SurfaceFlinger::captureDisplay(DisplayId displayId,
+void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
                                     const sp<IScreenCaptureListener>& captureListener) {
     ui::LayerStack layerStack;
     wp<const DisplayDevice> displayWeak;
@@ -7576,10 +7699,22 @@
         size = display->getLayerStackSpaceRect().getSize();
     }
 
+    size.width *= args.frameScaleX;
+    size.height *= args.frameScaleY;
+
+    // We could query a real value for this but it'll be a long, long time until we support
+    // displays that need upwards of 1GB per buffer so...
+    constexpr auto kMaxTextureSize = 16384;
+    if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
+        size.height >= kMaxTextureSize) {
+        ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
+    }
+
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, Rect(), size, ui::Dataspace::UNKNOWN,
-                                         false /* useIdentityTransform */,
-                                         false /* hintForSeamlessTransition */,
+        return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
+                                         args.hintForSeamlessTransition,
                                          false /* captureSecureLayers */);
     });
 
@@ -7603,8 +7738,8 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
-                        ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale, captureListener);
+    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat,
+                        kAllowProtected, kGrayscale, captureListener);
 }
 
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
@@ -7623,8 +7758,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);
@@ -7636,13 +7774,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;
@@ -7682,7 +7813,7 @@
     }
 
     bool childrenOnly = args.childrenOnly;
-    RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
+    RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> std::unique_ptr<RenderArea> {
         ui::Transform layerTransform;
         Rect layerBufferSize;
         if (mLayerLifecycleManagerEnabled) {
@@ -7785,12 +7916,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),
@@ -7810,20 +7940,19 @@
                                                  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(
+            [=, this, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
                 ScreenCaptureResults captureResults;
                 std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
@@ -7838,9 +7967,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) {
@@ -7867,9 +7996,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();
@@ -7884,14 +8012,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();
@@ -7972,9 +8092,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());
@@ -8008,7 +8128,8 @@
                                         .regionSampling = regionSampling,
                                         .treat170mAsSrgb = mTreat170mAsSrgb,
                                         .dimInGammaSpaceForEnhancedScreenshots =
-                                                dimInGammaSpaceForEnhancedScreenshots});
+                                                dimInGammaSpaceForEnhancedScreenshots,
+                                        .isProtected = isProtected});
 
         const float colorSaturation = grayscale ? 0 : 1;
         compositionengine::CompositionRefreshArgs refreshArgs{
@@ -8204,18 +8325,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();
     }
 
@@ -8255,7 +8377,7 @@
         return BAD_VALUE;
     }
 
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set desired display modes for invalid display token %p",
@@ -8372,17 +8494,25 @@
     return genericLayerMetadataKeyMap;
 }
 
-status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) {
+status_t SurfaceFlinger::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
     PhysicalDisplayId displayId = [&]() {
         Mutex::Autolock lock(mStateLock);
         return getDefaultDisplayDeviceLocked()->getPhysicalId();
     }();
 
-    mScheduler->setGameModeRefreshRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
+    mScheduler->setGameModeFrameRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
     mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::setGameDefaultFrameRateOverride(uid_t uid, float frameRate) {
+    if (FlagManager::getInstance().game_default_frame_rate()) {
+        mScheduler->setGameDefaultFrameRateForUid(
+                FrameRateOverride{static_cast<uid_t>(uid), frameRate});
+    }
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::updateSmallAreaDetection(
         std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
     mScheduler->updateSmallAreaDetection(appIdThresholdMappings);
@@ -8397,7 +8527,8 @@
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
     bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
     for (const auto& [id, display] : mPhysicalDisplays) {
-        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
+            FlagManager::getInstance().refresh_rate_overlay_on_external_display()) {
             if (const auto device = getDisplayDeviceLocked(id)) {
                 const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD(
                                                    kMainThreadContext) {
@@ -8470,7 +8601,8 @@
 }
 
 int SurfaceFlinger::getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const {
-    const auto vsyncConfig = mVsyncConfiguration->getConfigsForRefreshRate(refreshRate).late;
+    const auto vsyncConfig =
+            mScheduler->getVsyncConfiguration().getConfigsForRefreshRate(refreshRate).late;
     const auto presentLatency = vsyncConfig.appWorkDuration + vsyncConfig.sfWorkDuration;
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
@@ -8551,7 +8683,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;
 
@@ -8563,7 +8695,7 @@
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
+    mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
 
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
@@ -8575,9 +8707,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);
 }
@@ -8601,6 +8733,40 @@
     return NO_ERROR;
 }
 
+void SurfaceFlinger::updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel,
+                                      int32_t maxLevel) {
+    if (!FlagManager::getInstance().connected_display()) {
+        return;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto idOpt = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
+    if (!idOpt) {
+        ALOGE("No display found for HDCP level changed event: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    const bool isInternalDisplay =
+            mPhysicalDisplays.get(*idOpt).transform(&PhysicalDisplay::isInternal).value_or(false);
+    if (isInternalDisplay) {
+        ALOGW("Unexpected HDCP level changed for internal display: connected=%d, max=%d for "
+              "display=%" PRIu64,
+              connectedLevel, maxLevel, hwcDisplayId);
+        return;
+    }
+
+    static_cast<void>(mScheduler->schedule([this, displayId = *idOpt, connectedLevel, maxLevel]() {
+        if (const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayId))) {
+            Mutex::Autolock lock(mStateLock);
+            display->setSecure(connectedLevel >= 2 /* HDCP_V1 */);
+        }
+        mScheduler->onHdcpLevelsChanged(mAppConnectionHandle, displayId, connectedLevel, maxLevel);
+    }));
+}
+
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
         BufferData& bufferData, const char* layerName, uint64_t transactionId) {
     if (bufferData.buffer &&
@@ -8774,9 +8940,9 @@
                     }
 
                     auto it = mLegacyLayers.find(snapshot->sequence);
-                    LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
-                                        "Couldnt find layer object for %s",
-                                        snapshot->getDebugString().c_str());
+                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                    "Couldnt find layer object for %s",
+                                                    snapshot->getDebugString().c_str());
                     auto& legacyLayer = it->second;
                     sp<LayerFE> layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path);
                     snapshot->fps = getLayerFramerate(currentTime, snapshot->sequence);
@@ -8845,9 +9011,9 @@
                     }
 
                     auto it = mLegacyLayers.find(snapshot->sequence);
-                    LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(),
-                                        "Couldnt find layer object for %s",
-                                        snapshot->getDebugString().c_str());
+                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                    "Couldnt find layer object for %s",
+                                                    snapshot->getDebugString().c_str());
                     Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get();
                     sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name);
                     layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
@@ -8922,6 +9088,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 =
@@ -9035,6 +9205,11 @@
     setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded | eTraversalNeeded);
 }
 
+void SurfaceFlinger::sfdo_forceClientComposition(bool enabled) {
+    mDebugDisableHWC = enabled;
+    scheduleRepaint();
+}
+
 // gui::ISurfaceComposer
 
 binder::Status SurfaceComposerAIDL::bootFinished() {
@@ -9064,7 +9239,7 @@
     const sp<Client> client = sp<Client>::make(mFlinger);
     if (client->initCheck() == NO_ERROR) {
         *outClient = client;
-        if (flags::misc1()) {
+        if (FlagManager::getInstance().misc1()) {
             const int policy = SCHED_FIFO;
             client->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
         }
@@ -9397,13 +9572,14 @@
 }
 
 binder::Status SurfaceComposerAIDL::captureDisplayById(
-        int64_t displayId, const sp<IScreenCaptureListener>& captureListener) {
+        int64_t displayId, const CaptureArgs& args,
+        const sp<IScreenCaptureListener>& captureListener) {
     // status_t status;
     IPCThreadState* ipc = IPCThreadState::self();
     const int uid = ipc->getCallingUid();
     if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
-        mFlinger->captureDisplay(*id, captureListener);
+        mFlinger->captureDisplay(*id, args, captureListener);
     } else {
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
@@ -9721,13 +9897,25 @@
     return binder::Status::ok();
 }
 
-binder::Status SurfaceComposerAIDL::setOverrideFrameRate(int32_t uid, float frameRate) {
+binder::Status SurfaceComposerAIDL::setGameModeFrameRateOverride(int32_t uid, float frameRate) {
     status_t status;
     const int c_uid = IPCThreadState::self()->getCallingUid();
     if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
-        status = mFlinger->setOverrideFrameRate(uid, frameRate);
+        status = mFlinger->setGameModeFrameRateOverride(uid, frameRate);
     } else {
-        ALOGE("setOverrideFrameRate() permission denied for uid: %d", c_uid);
+        ALOGE("setGameModeFrameRateOverride() permission denied for uid: %d", c_uid);
+        status = PERMISSION_DENIED;
+    }
+    return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setGameDefaultFrameRateOverride(int32_t uid, float frameRate) {
+    status_t status;
+    const int c_uid = IPCThreadState::self()->getCallingUid();
+    if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
+        status = mFlinger->setGameDefaultFrameRateOverride(uid, frameRate);
+    } else {
+        ALOGE("setGameDefaultFrameRateOverride() permission denied for uid: %d", c_uid);
         status = PERMISSION_DENIED;
     }
     return binderStatusFromStatusT(status);
@@ -9753,6 +9941,11 @@
     return binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::forceClientComposition(bool enabled) {
+    mFlinger->sfdo_forceClientComposition(enabled);
+    return binder::Status::ok();
+}
+
 binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                                              const std::vector<float>& thresholds) {
     status_t status;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 96b67b8..5846214 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -21,6 +21,8 @@
  * NOTE: Make sure this file doesn't include  anything from <gl/ > or <gl2/ >
  */
 
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/thread_annotations.h>
 #include <android/gui/BnSurfaceComposer.h>
 #include <android/gui/DisplayStatInfo.h>
@@ -63,13 +65,13 @@
 #include <scheduler/interface/ICompositor.h>
 #include <ui/FenceResult.h>
 
+#include <common/FlagManager.h>
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerLifecycleManager.h"
@@ -77,9 +79,9 @@
 #include "FrontEnd/LayerSnapshotBuilder.h"
 #include "FrontEnd/TransactionHandler.h"
 #include "LayerVector.h"
+#include "MutexUtils.h"
 #include "Scheduler/ISchedulerCallback.h"
 #include "Scheduler/RefreshRateSelector.h"
-#include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/Scheduler.h"
 #include "SurfaceFlingerFactory.h"
 #include "ThreadContext.h"
@@ -106,6 +108,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 +133,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;
@@ -195,7 +199,8 @@
                        private HWC2::ComposerCallback,
                        private ICompositor,
                        private scheduler::ISchedulerCallback,
-                       private compositionengine::ICEPowerCallback {
+                       private compositionengine::ICEPowerCallback,
+                       private scheduler::IVsyncTrackerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -475,22 +480,46 @@
         return std::bind(std::forward<F>(dump), _3);
     }
 
+    Dumper lockedDumper(Dumper dump) {
+        return [this, dump](const DumpArgs& args, bool asProto, std::string& result) -> void {
+            TimedLock lock(mStateLock, s2ns(1), __func__);
+            if (!lock.locked()) {
+                base::StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
+                                    strerror(-lock.status), lock.status);
+            }
+            dump(args, asProto, result);
+        };
+    }
+
     template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
     Dumper dumper(F dump) {
         using namespace std::placeholders;
-        return std::bind(dump, this, _3);
+        return lockedDumper(std::bind(dump, this, _3));
     }
 
     template <typename F>
     Dumper argsDumper(F dump) {
         using namespace std::placeholders;
-        return std::bind(dump, this, _1, _3);
+        return lockedDumper(std::bind(dump, this, _1, _3));
     }
 
     template <typename F>
     Dumper protoDumper(F dump) {
         using namespace std::placeholders;
-        return std::bind(dump, this, _1, _2, _3);
+        return lockedDumper(std::bind(dump, this, _1, _2, _3));
+    }
+
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper mainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        Dumper dumper = std::bind(dump, this, _3);
+        return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
+            mScheduler
+                    ->schedule(
+                            [&args, asProto, &result, dumper]() FTL_FAKE_GUARD(kMainThreadContext)
+                                    FTL_FAKE_GUARD(mStateLock) { dumper(args, asProto, result); })
+                    .get();
+        };
     }
 
     // Maximum allowed number of display frames that can be set through backdoor
@@ -530,7 +559,7 @@
             const sp<IBinder>& layerHandle = nullptr);
 
     void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
-    void captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&);
     void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
@@ -604,7 +633,9 @@
     status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
                                   const gui::FrameTimelineInfo& frameTimelineInfo);
 
-    status_t setOverrideFrameRate(uid_t uid, float frameRate);
+    status_t setGameModeFrameRateOverride(uid_t uid, float frameRate);
+
+    status_t setGameDefaultFrameRateOverride(uid_t uid, float frameRate);
 
     status_t updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
 
@@ -622,13 +653,15 @@
     status_t getStalledTransactionInfo(
             int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result);
 
+    void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
+
     // Implements IBinder::DeathRecipient.
     void binderDied(const wp<IBinder>& who) override;
 
     // 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;
@@ -656,6 +689,10 @@
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
+    // IVsyncTrackerCallback overrides
+    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);
 
@@ -681,8 +718,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);
@@ -690,9 +726,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);
@@ -747,9 +784,6 @@
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
 
-    void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext);
-    void updatePhaseConfiguration(Fps) REQUIRES(mStateLock);
-
     /*
      * Transactions
      */
@@ -848,11 +882,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
@@ -982,8 +1016,8 @@
     /*
      * Compositing
      */
-    void postComposition(PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters&,
-                         nsecs_t presentStartTime) REQUIRES(kMainThreadContext);
+    void onCompositionPresented(PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters&,
+                                nsecs_t presentStartTime) REQUIRES(kMainThreadContext);
 
     /*
      * Display management
@@ -1073,8 +1107,8 @@
     /*
      * Debugging & dumpsys
      */
-    void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
-                       std::string& result) const REQUIRES(mStateLock);
+    void dumpAll(const DumpArgs& args, const std::string& compositionLayers,
+                 std::string& result) const EXCLUDES(mStateLock);
     void dumpHwcLayersMinidump(std::string& result) const REQUIRES(mStateLock, kMainThreadContext);
     void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
@@ -1096,7 +1130,8 @@
     void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
     void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
     void dumpHdrInfo(std::string& result) const REQUIRES(mStateLock);
-    void dumpFrontEnd(std::string& result);
+    void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext);
+    void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext);
 
     perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
     void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
@@ -1239,6 +1274,7 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
+    bool mIsHdcpViaNegVsync = false;
     bool mIsHotplugErrViaNegVsync = false;
 
     std::mutex mHotplugMutex;
@@ -1334,10 +1370,6 @@
     scheduler::ConnectionHandle mAppConnectionHandle;
     scheduler::ConnectionHandle mSfConnectionHandle;
 
-    // Stores phase offsets configured per refresh rate.
-    std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration;
-
-    std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
     scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext);
 
     bool mLumaSampling = true;
@@ -1421,8 +1453,6 @@
 
     const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
 
-    FlagManager mFlagManager;
-
     // returns the framerate of the layer with the given sequence ID
     float getLayerFramerate(nsecs_t now, int32_t id) const {
         return mScheduler->getLayerFramerate(now, id);
@@ -1434,7 +1464,7 @@
     bool mLegacyFrontEndEnabled = true;
 
     frontend::LayerLifecycleManager mLayerLifecycleManager;
-    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
     frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
 
     std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
@@ -1455,16 +1485,24 @@
     // Map of displayid to mirrorRoot
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
+    // NotifyExpectedPresentHint
+    struct NotifyExpectedPresentData {
+        // lastExpectedPresentTimestamp is read and write from multiple threads such as
+        // main thread, EventThread, MessageQueue. And is atomic for that reason.
+        std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+        Fps lastFrameInterval{};
+    };
+    std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
+
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt);
+
     void sfdo_enableRefreshRateOverlay(bool active);
     void sfdo_setDebugFlash(int delay);
     void sfdo_scheduleComposite();
     void sfdo_scheduleCommit();
-
-    // Trunk-Stable flags
-    bool mMiscFlagValue;
-    bool mConnectedDisplayFlagValue;
-    bool mMisc2FlagEarlyBootValue;
-    bool mMisc2FlagLateBootValue;
+    void sfdo_forceClientComposition(bool enabled);
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1510,7 +1548,8 @@
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
                                   const sp<IScreenCaptureListener>&) override;
-    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override;
+    binder::Status captureDisplayById(int64_t, const CaptureArgs&,
+                                      const sp<IScreenCaptureListener>&) override;
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override;
 
@@ -1570,11 +1609,13 @@
     binder::Status getDisplayDecorationSupport(
             const sp<IBinder>& displayToken,
             std::optional<gui::DisplayDecorationSupport>* outSupport) override;
-    binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override;
+    binder::Status setGameModeFrameRateOverride(int32_t uid, float frameRate) override;
+    binder::Status setGameDefaultFrameRateOverride(int32_t uid, float frameRate) override;
     binder::Status enableRefreshRateOverlay(bool active) override;
     binder::Status setDebugFlash(int delay) override;
     binder::Status scheduleComposite() override;
     binder::Status scheduleCommit() override;
+    binder::Status forceClientComposition(bool enabled) override;
     binder::Status updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                             const std::vector<float>& thresholds) override;
     binder::Status setSmallAreaDetectionThreshold(int32_t appId, float threshold) override;
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index f339d22..3b2bbb0 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -31,6 +31,9 @@
     },
     {
       "name": "CtsSurfaceControlTests"
+    },
+    {
+      "name": "CtsSurfaceControlTestsStaging"
     }
   ],
   "hwasan-presubmit": [
@@ -40,10 +43,5 @@
     {
       "name": "libsurfaceflinger_unittest"
     }
-  ],
-  "postsubmit": [
-    {
-      "name": "CtsSurfaceControlTestsStaging"
-    }
   ]
 }
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
index 25e768e..ed1e2ec 100644
--- a/services/surfaceflinger/Tracing/LayerDataSource.cpp
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -51,8 +51,9 @@
     if (config.has_mode() && config.mode() != LayerTracing::Mode::MODE_UNSPECIFIED) {
         mMode = static_cast<LayerTracing::Mode>(config.mode());
     } else {
-        mMode = LayerTracing::Mode::MODE_GENERATED;
-        ALOGD("Received config with unspecified 'mode'. Using 'GENERATED' as default");
+        mMode = LayerTracing::Mode::MODE_GENERATED_BUGREPORT_ONLY;
+        ALOGD("Received config with unspecified 'mode'."
+              " Using 'MODE_GENERATED_BUGREPORT_ONLY' as default");
     }
 
     mFlags = 0;
@@ -68,10 +69,16 @@
     }
 }
 
-void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs&) {
-    ALOGD("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs& args) {
+    ALOGD("Received OnFlush event"
+          " (mode = 0x%02x, flags = 0x%02x, reason = 0x%" PRIx64 ", clone_target = 0x%0" PRIx64 ")",
+          mMode, mFlags, args.flush_flags.reason(), args.flush_flags.clone_target());
+
+    bool isBugreport = args.flush_flags.reason() == perfetto::FlushFlags::Reason::kTraceClone &&
+            args.flush_flags.clone_target() == perfetto::FlushFlags::CloneTarget::kBugreport;
+
     if (auto* p = mLayerTracing.load()) {
-        p->onFlush(mMode, mFlags);
+        p->onFlush(mMode, mFlags, isBugreport);
     }
 }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index 403e105..41bcdf0 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -67,9 +67,27 @@
             break;
         }
         case Mode::MODE_GENERATED: {
+            // This tracing mode processes the buffer of transactions (owned by TransactionTracing),
+            // generates layers snapshots and writes them to perfetto. This happens every time an
+            // OnFlush event is received.
             ALOGD("Started generated tracing (waiting for OnFlush event to generated layers)");
             break;
         }
+        case Mode::MODE_GENERATED_BUGREPORT_ONLY: {
+            // Same as MODE_GENERATED, but only when the received OnFlush event is due to a
+            // bugreport being taken. This mode exists because the generated layers trace is very
+            // large (hundreds of MB), hence we want to include it only in bugreports and not in
+            // field uploads.
+            //
+            // Note that perfetto communicates only whether the OnFlush event is due to a bugreport
+            // or not, hence we need an additional "bugreport only" tracing mode.
+            // If perfetto had communicated when the OnFlush is due to a field upload, then we could
+            // have had a single "generated" tracing mode that would have been a noop in case of
+            // field uploads.
+            ALOGD("Started 'generated bugreport only' tracing"
+                  " (waiting for bugreport's OnFlush event to generate layers)");
+            break;
+        }
         case Mode::MODE_DUMP: {
             auto snapshot = mTakeLayersSnapshotProto(flags);
             addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
@@ -82,10 +100,18 @@
     }
 }
 
-void LayerTracing::onFlush(Mode mode, uint32_t flags) {
+void LayerTracing::onFlush(Mode mode, uint32_t flags, bool isBugreport) {
     // In "generated" mode process the buffer of transactions (owned by TransactionTracing),
-    // generate a sequence of layers snapshots and write them to perfetto.
-    if (mode != Mode::MODE_GENERATED) {
+    // generate layers snapshots and write them to perfetto.
+    if (mode != Mode::MODE_GENERATED && mode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
+        ALOGD("Skipping layers trace generation (not a 'generated' tracing session)");
+        return;
+    }
+
+    // In "generated bugreport only" mode skip the layers snapshot generation
+    // if the perfetto's OnFlush event is not due to a bugreport being taken.
+    if (mode == Mode::MODE_GENERATED_BUGREPORT_ONLY && !isBugreport) {
+        ALOGD("Skipping layers trace generation (not a bugreport OnFlush event)");
         return;
     }
 
@@ -147,14 +173,23 @@
 }
 
 void LayerTracing::writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot,
-                                           Mode mode) {
+                                           Mode srcMode) {
     const auto snapshotBytes = snapshot.SerializeAsString();
 
     LayerDataSource::Trace([&](LayerDataSource::TraceContext context) {
-        if (mode != context.GetCustomTlsState()->mMode) {
+        auto dstMode = context.GetCustomTlsState()->mMode;
+        if (srcMode == Mode::MODE_GENERATED) {
+            // Layers snapshots produced by LayerTraceGenerator have srcMode == MODE_GENERATED
+            // and should be written to tracing sessions with MODE_GENERATED
+            // or MODE_GENERATED_BUGREPORT_ONLY.
+            if (dstMode != Mode::MODE_GENERATED && dstMode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
+                return;
+            }
+        } else if (srcMode != dstMode) {
             return;
         }
-        if (!checkAndUpdateLastVsyncIdWrittenToPerfetto(mode, snapshot.vsync_id())) {
+
+        if (!checkAndUpdateLastVsyncIdWrittenToPerfetto(srcMode, snapshot.vsync_id())) {
             return;
         }
         {
@@ -176,7 +211,7 @@
     // In some situations (e.g. two bugreports taken shortly one after the other) the generated
     // sequence of layers snapshots might overlap. Here we check the snapshot's vsyncid to make
     // sure that in generated tracing mode a given snapshot is written only once to perfetto.
-    if (mode != Mode::MODE_GENERATED) {
+    if (mode != Mode::MODE_GENERATED && mode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
         return true;
     }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index fe7f06d..2895ba7 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -55,7 +55,9 @@
  * and written to perfetto.
  *
  *
- * E.g. start active mode tracing:
+ * E.g. start active mode tracing
+ * (replace mode value with MODE_DUMP, MODE_GENERATED or MODE_GENERATED_BUGREPORT_ONLY to enable
+ * different tracing modes):
  *
    adb shell -t perfetto \
      -c - --txt \
@@ -79,7 +81,7 @@
            }
        }
    }
-   EOF
+EOF
  *
  */
 class LayerTracing {
@@ -106,7 +108,7 @@
     // Start event from perfetto data source
     void onStart(Mode mode, uint32_t flags);
     // Flush event from perfetto data source
-    void onFlush(Mode mode, uint32_t flags);
+    void onFlush(Mode mode, uint32_t flags, bool isBugreport);
     // Stop event from perfetto data source
     void onStop(Mode mode);
 
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 9d6d87e..696f348 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;
@@ -441,6 +438,7 @@
     for (auto& [layerStack, displayInfo] : mStartingDisplayInfos) {
         entryProto.mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id));
     }
+    entryProto.set_displays_changed(true);
 
     return entryProto;
 }
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index ddbf3e4..6a66fff 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -218,6 +218,13 @@
     friend class Singleton<TransactionTracing>;
     std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
             [](const std::string&, bool) {};
+    std::atomic<bool> mEnabled{true};
+
+    void doInvoke(const std::string& filename, bool overwrite) {
+        if (mEnabled) {
+            mWriterFunction(filename, overwrite);
+        }
+    };
 
 public:
     void setWriterFunction(
@@ -225,12 +232,15 @@
         mWriterFunction = std::move(function);
     }
     void invoke(const std::string& prefix, bool overwrite) {
-        mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+        doInvoke(TransactionTracing::getFilePath(prefix), overwrite);
     }
     /* pass in a complete file path for testing */
     void invokeForTest(const std::string& filename, bool overwrite) {
-        mWriterFunction(filename, overwrite);
+        doInvoke(filename, overwrite);
     }
+    /* hacky way to avoid generating traces when converting transaction trace to layers trace. */
+    void disable() { mEnabled.store(false); }
+    void enable() { mEnabled.store(true); }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index c2d1954..617ea2c 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -40,9 +40,24 @@
 namespace android {
 using namespace ftl::flag_operators;
 
+namespace {
+class ScopedTraceDisabler {
+public:
+    ScopedTraceDisabler() { TransactionTraceWriter::getInstance().disable(); }
+    ~ScopedTraceDisabler() { TransactionTraceWriter::getInstance().enable(); }
+};
+} // namespace
+
 bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
                                    std::uint32_t traceFlags, LayerTracing& layerTracing,
                                    bool onlyLastEntry) {
+    // We are generating the layers trace by replaying back a set of transactions. If the
+    // transactions have unexpected states, we may generate a transaction trace to debug
+    // the unexpected state. This is silly. So we disable it by poking the
+    // TransactionTraceWriter. This is really a hack since we should manage our depenecies a
+    // little better.
+    ScopedTraceDisabler fatalErrorTraceDisabler;
+
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
         return false;
@@ -52,7 +67,7 @@
 
     // frontend
     frontend::LayerLifecycleManager lifecycleManager;
-    frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
     frontend::LayerSnapshotBuilder snapshotBuilder;
     ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
 
@@ -119,12 +134,10 @@
         lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true);
         lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true);
 
-        if (lifecycleManager.getGlobalChanges().test(
-                    frontend::RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(lifecycleManager.getLayers(),
-                                    lifecycleManager.getDestroyedLayers());
-        }
+        // update hierarchy
+        hierarchyBuilder.update(lifecycleManager);
 
+        // update snapshots
         frontend::LayerSnapshotBuilder::Args args{.root = hierarchyBuilder.getHierarchy(),
                                                   .layerLifecycleManager = lifecycleManager,
                                                   .displays = displayInfos,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 3587a72..6a155c1 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -158,7 +158,7 @@
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
-        FrameEventHistoryStats eventStats(handle->frameNumber,
+        FrameEventHistoryStats eventStats(handle->frameNumber, handle->previousFrameNumber,
                                           handle->gpuCompositionDoneFence->getSnapshot().fence,
                                           handle->compositorTiming, handle->refreshStartTime,
                                           handle->dequeueReadyTime);
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 3074795..245398f 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -56,6 +56,7 @@
     nsecs_t refreshStartTime = 0;
     nsecs_t dequeueReadyTime = 0;
     uint64_t frameNumber = 0;
+    uint64_t previousFrameNumber = 0;
     ReleaseCallbackId previousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
 };
 
diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h
index ee94217..62d2ebb 100644
--- a/services/surfaceflinger/Utils/Dumper.h
+++ b/services/surfaceflinger/Utils/Dumper.h
@@ -35,6 +35,8 @@
 
     void eol() { mOut += '\n'; }
 
+    std::string& out() { return mOut; }
+
     void dump(std::string_view name, std::string_view value = {}) {
         using namespace std::string_view_literals;
 
diff --git a/services/surfaceflinger/Utils/FlagUtils.h b/services/surfaceflinger/Utils/FlagUtils.h
deleted file mode 100644
index 8435f04..0000000
--- a/services/surfaceflinger/Utils/FlagUtils.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android-base/properties.h>
-#include <com_android_graphics_surfaceflinger_flags.h>
-#include <string>
-
-namespace android::flagutils {
-
-using namespace std::literals::string_literals;
-using namespace com::android::graphics::surfaceflinger;
-
-inline bool vrrConfigEnabled() {
-    static const bool enable_vrr_config =
-            base::GetBoolProperty("debug.sf.enable_vrr_config"s, false);
-    return flags::vrr_config() || enable_vrr_config;
-}
-} // namespace android::flagutils
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
new file mode 100644
index 0000000..5ef22b5
--- /dev/null
+++ b/services/surfaceflinger/common/Android.bp
@@ -0,0 +1,48 @@
+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_defaults {
+    name: "libsurfaceflinger_common_defaults",
+    defaults: [
+        "android.hardware.graphics.composer3-ndk_shared",
+        "surfaceflinger_defaults",
+    ],
+    shared_libs: [
+        "libSurfaceFlingerProp",
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "librenderengine",
+    ],
+    srcs: [
+        "FlagManager.cpp",
+    ],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags",
+    ],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common_test",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags_test",
+    ],
+}
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
new file mode 100644
index 0000000..a27e100
--- /dev/null
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2021 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 <common/FlagManager.h>
+
+#include <SurfaceFlingerProperties.sysprop.h>
+#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <server_configurable_flags/get_flags.h>
+#include <cinttypes>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+namespace android {
+using namespace com::android::graphics::surfaceflinger;
+
+static constexpr const char* kExperimentNamespace = "surface_flinger_native_boot";
+
+std::unique_ptr<FlagManager> FlagManager::mInstance;
+std::once_flag FlagManager::mOnce;
+
+FlagManager::FlagManager(ConstructorTag) {}
+FlagManager::~FlagManager() = default;
+
+namespace {
+std::optional<bool> parseBool(const char* str) {
+    base::ParseBoolResult parseResult = base::ParseBool(str);
+    switch (parseResult) {
+        case base::ParseBoolResult::kTrue:
+            return std::make_optional(true);
+        case base::ParseBoolResult::kFalse:
+            return std::make_optional(false);
+        case base::ParseBoolResult::kError:
+            return std::nullopt;
+    }
+}
+
+bool getFlagValue(std::function<bool()> getter, std::optional<bool> overrideValue) {
+    if (overrideValue.has_value()) {
+        return *overrideValue;
+    }
+
+    return getter();
+}
+
+} // namespace
+
+const FlagManager& FlagManager::getInstance() {
+    return getMutableInstance();
+}
+
+FlagManager& FlagManager::getMutableInstance() {
+    std::call_once(mOnce, [&] {
+        LOG_ALWAYS_FATAL_IF(mInstance, "Instance already created");
+        mInstance = std::make_unique<FlagManager>(ConstructorTag{});
+    });
+
+    return *mInstance;
+}
+
+void FlagManager::markBootCompleted() {
+    mBootCompleted = true;
+}
+
+void FlagManager::setUnitTestMode() {
+    mUnitTestMode = true;
+
+    // Also set boot completed as we don't really care about it in unit testing
+    mBootCompleted = true;
+}
+
+void FlagManager::dumpFlag(std::string& result, bool readonly, const char* name,
+                           std::function<bool()> getter) const {
+    if (readonly || mBootCompleted) {
+        base::StringAppendF(&result, "%s: %s\n", name, getter() ? "true" : "false");
+    } else {
+        base::StringAppendF(&result, "%s: in progress (still booting)\n", name);
+    }
+}
+
+void FlagManager::dump(std::string& result) const {
+#define DUMP_FLAG_INTERVAL(name, readonly) \
+    dumpFlag(result, (readonly), #name, std::bind(&FlagManager::name, this))
+#define DUMP_SERVER_FLAG(name) DUMP_FLAG_INTERVAL(name, false)
+#define DUMP_READ_ONLY_FLAG(name) DUMP_FLAG_INTERVAL(name, true)
+
+    base::StringAppendF(&result, "FlagManager values: \n");
+
+    /// Legacy server flags ///
+    DUMP_SERVER_FLAG(use_adpf_cpu_hint);
+    DUMP_SERVER_FLAG(use_skia_tracing);
+
+    /// Trunk stable server flags ///
+    DUMP_SERVER_FLAG(late_boot_misc2);
+    DUMP_SERVER_FLAG(dont_skip_on_early);
+    DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+
+    /// Trunk stable readonly flags ///
+    DUMP_READ_ONLY_FLAG(connected_display);
+    DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+    DUMP_READ_ONLY_FLAG(misc1);
+    DUMP_READ_ONLY_FLAG(vrr_config);
+    DUMP_READ_ONLY_FLAG(hotplug2);
+    DUMP_READ_ONLY_FLAG(hdcp_level_hal);
+    DUMP_READ_ONLY_FLAG(multithreaded_present);
+    DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
+    DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
+    DUMP_READ_ONLY_FLAG(cache_when_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);
+    DUMP_READ_ONLY_FLAG(game_default_frame_rate);
+    DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
+#undef DUMP_READ_ONLY_FLAG
+#undef DUMP_SERVER_FLAG
+#undef DUMP_FLAG_INTERVAL
+}
+
+std::optional<bool> FlagManager::getBoolProperty(const char* property) const {
+    return parseBool(base::GetProperty(property, "").c_str());
+}
+
+bool FlagManager::getServerConfigurableFlag(const char* experimentFlagName) const {
+    const auto value = server_configurable_flags::GetServerConfigurableFlag(kExperimentNamespace,
+                                                                            experimentFlagName, "");
+    const auto res = parseBool(value.c_str());
+    return res.has_value() && res.value();
+}
+
+#define FLAG_MANAGER_LEGACY_SERVER_FLAG(name, syspropOverride, serverFlagName)              \
+    bool FlagManager::name() const {                                                        \
+        LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
+                            "Can't read %s before boot completed as it is server writable", \
+                            __func__);                                                      \
+        const auto debugOverride = getBoolProperty(syspropOverride);                        \
+        if (debugOverride.has_value()) return debugOverride.value();                        \
+        return getServerConfigurableFlag(serverFlagName);                                   \
+    }
+
+#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted)                \
+    bool FlagManager::name() const {                                                            \
+        if (checkForBootCompleted) {                                                            \
+            LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
+                                "Can't read %s before boot completed as it is server writable", \
+                                __func__);                                                      \
+        }                                                                                       \
+        static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);      \
+        static const bool value = getFlagValue([] { return flags::name(); }, debugOverride);    \
+        if (mUnitTestMode) {                                                                    \
+            /*                                                                                  \
+             * When testing, we don't want to rely on the cached `value` or the debugOverride.  \
+             */                                                                                 \
+            return flags::name();                                                               \
+        }                                                                                       \
+        return value;                                                                           \
+    }
+
+#define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true)
+
+#define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false)
+
+/// Legacy server flags ///
+FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
+FLAG_MANAGER_LEGACY_SERVER_FLAG(use_adpf_cpu_hint, "debug.sf.enable_adpf_cpu_hint",
+                                "AdpfFeature__adpf_cpu_hint")
+FLAG_MANAGER_LEGACY_SERVER_FLAG(use_skia_tracing, PROPERTY_SKIA_ATRACE_ENABLED,
+                                "SkiaTracingFeature__use_skia_tracing")
+
+/// Trunk stable readonly flags ///
+FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
+FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
+FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "")
+FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
+FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
+FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
+FLAG_MANAGER_READ_ONLY_FLAG(cache_when_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")
+FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+
+/// Trunk stable server flags ///
+FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
+FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+
+/// Exceptions ///
+bool FlagManager::dont_skip_on_early() const {
+    // Even though this is a server writable flag, we do call it before boot completed, but that's
+    // fine since the decision is done per frame. We can't do caching though.
+    return flags::dont_skip_on_early();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
new file mode 100644
index 0000000..2f2895c
--- /dev/null
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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 <cstdint>
+#include <mutex>
+#include <optional>
+#include <string>
+
+namespace android {
+// Manages flags for SurfaceFlinger, including default values, system properties, and Mendel
+// experiment configuration values. Can be called from any thread.
+class FlagManager {
+private:
+    // Effectively making the constructor private, while allowing std::make_unique to work
+    struct ConstructorTag {};
+
+public:
+    static const FlagManager& getInstance();
+    static FlagManager& getMutableInstance();
+
+    FlagManager(ConstructorTag);
+    virtual ~FlagManager();
+
+    void markBootCompleted();
+    void dump(std::string& result) const;
+
+    void setUnitTestMode();
+
+    /// Legacy server flags ///
+    bool test_flag() const;
+    bool use_adpf_cpu_hint() const;
+    bool use_skia_tracing() const;
+
+    /// Trunk stable server flags ///
+    bool late_boot_misc2() const;
+    bool dont_skip_on_early() const;
+    bool refresh_rate_overlay_on_external_display() const;
+
+    /// Trunk stable readonly flags ///
+    bool connected_display() const;
+    bool enable_small_area_detection() const;
+    bool misc1() const;
+    bool vrr_config() const;
+    bool hotplug2() const;
+    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_when_source_crop_layer_only_moved() const;
+    bool enable_fro_dependent_features() const;
+    bool display_protected() const;
+    bool fp16_client_target() const;
+    bool game_default_frame_rate() const;
+    bool enable_layer_command_batching() const;
+
+protected:
+    // overridden for unit tests
+    virtual std::optional<bool> getBoolProperty(const char*) const;
+    virtual bool getServerConfigurableFlag(const char*) const;
+
+private:
+    friend class TestableFlagManager;
+
+    FlagManager(const FlagManager&) = delete;
+
+    void dumpFlag(std::string& result, bool readonly, const char* name,
+                  std::function<bool()> getter) const;
+
+    std::atomic_bool mBootCompleted = false;
+    std::atomic_bool mUnitTestMode = false;
+
+    static std::unique_ptr<FlagManager> mInstance;
+    static std::once_flag mOnce;
+};
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
similarity index 91%
rename from services/surfaceflinger/tests/unittests/FlagUtils.h
rename to services/surfaceflinger/common/include/common/test/FlagUtils.h
index 7103684..550c70d 100644
--- a/services/surfaceflinger/tests/unittests/FlagUtils.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h
@@ -16,12 +16,16 @@
 
 #pragma once
 
+#include <common/FlagManager.h>
+
 #define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
 
 namespace android {
 class TestFlagSetter {
 public:
     TestFlagSetter(bool (*getter)(), void((*setter)(bool)), bool flagValue) {
+        FlagManager::getMutableInstance().setUnitTestMode();
+
         const bool initialValue = getter();
         setter(flagValue);
         mResetFlagValue = [=] { setter(initialValue); };
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index 0f9060d..ab3b352 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -31,6 +31,7 @@
     ],
     static_libs: [
         "android.hardware.graphics.composer@2.1-resources",
+        "libc++fs",
         "libgmock",
         "libgui_mocks",
         "libgmock_ndk",
@@ -38,6 +39,7 @@
         "libgtest_ndk_c++",
         "libgmock_main_ndk",
         "librenderengine_mocks",
+        "libsurfaceflinger_common",
         "perfetto_trace_protos",
         "libcompositionengine_mocks",
         "perfetto_trace_protos",
@@ -73,9 +75,17 @@
     ],
     fuzz_config: {
         cc: [
-            "android-media-fuzzing-reports@google.com",
+            "android-cogs-eng@google.com",
         ],
-        componentid: 155276,
+        componentid: 1075131,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libsurfaceflinger library",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
     },
 }
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index f22315a..68237c8 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -163,16 +163,22 @@
 
 void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
     uint32_t outNumTypes, outNumRequests;
-    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
-                              &outNumRequests);
+    const auto frameIntervalRange =
+            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+                                                 Fps::fromValue(120).getPeriodNsecs());
+    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+                              &outNumTypes, &outNumRequests);
 }
 
 void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
                                                      Display display) {
     int32_t outPresentFence;
     uint32_t outNumTypes, outNumRequests, state;
-    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
-                                       &outNumRequests, &outPresentFence, &state);
+    const auto frameIntervalRange =
+            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+                                                 Fps::fromValue(120).getPeriodNsecs());
+    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+                                       &outNumTypes, &outNumRequests, &outPresentFence, &state);
 }
 
 void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
@@ -223,7 +229,10 @@
     mHwc.getDeviceCompositionChanges(halDisplayID,
                                      mFdp.ConsumeBool() /*frameUsesClientComposition*/,
                                      std::chrono::steady_clock::now(),
-                                     mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
+                                     mFdp.ConsumeIntegral<nsecs_t>(),
+                                     Fps::fromValue(
+                                             mFdp.ConsumeFloatingPointInRange<float>(1.f, 120.f)),
+                                     &outChanges);
 }
 
 void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
@@ -277,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));
@@ -485,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);
@@ -521,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);
@@ -552,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_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
index 8978971..ce8d47e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
@@ -30,12 +30,6 @@
 constexpr int32_t kConfigDuration = 500;
 constexpr int32_t kBufferSize = 1024;
 constexpr int32_t kTimeOffset = 100000;
-constexpr perfetto::BackendType backendTypes[] = {
-        perfetto::kUnspecifiedBackend,
-        perfetto::kInProcessBackend,
-        perfetto::kSystemBackend,
-        perfetto::kCustomBackend,
-};
 
 class FrameTracerFuzzer {
 public:
@@ -71,8 +65,7 @@
     auto* dsCfg = cfg.add_data_sources()->mutable_config();
     dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource);
 
-    auto tracingSession =
-            perfetto::Tracing::NewTrace(mFdp.PickValueInArray<perfetto::BackendType>(backendTypes));
+    auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
     tracingSession->Setup(cfg);
     return tracingSession;
 }
@@ -115,11 +108,15 @@
     std::vector<int32_t> layerIds =
             generateLayerIds(mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds));
 
+    std::unique_ptr<perfetto::TracingSession> tracingSession;
     while (mFdp.remaining_bytes()) {
         auto invokeFrametracerAPI = mFdp.PickValueInArray<const std::function<void()>>({
                 [&]() { mFrameTracer->registerDataSource(); },
                 [&]() {
-                    auto tracingSession = getTracingSessionForTest();
+                    if (tracingSession) {
+                        tracingSession->StopBlocking();
+                    }
+                    tracingSession = getTracingSessionForTest();
                     tracingSession->StartBlocking();
                 },
                 [&]() { traceTimestamp(layerIds, layerIds.size()); },
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index c4077df..fa79956 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -224,17 +224,13 @@
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
-                              std::make_shared<android::mock::VSyncTracker>(), selectorPtr,
-                              std::move(modulatorPtr), callback) {}
-
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       VsyncSchedule::TrackerPtr tracker,
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
+                      surfaceflinger::Factory& factory, TimeStats& timeStats,
+                      ISchedulerCallback& callback, IVsyncTrackerCallback& vsyncTrackerCallback)
+          : Scheduler(*this, callback, Feature::kContentDetection, factory,
+                      selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplayInternal(displayId, std::move(selectorPtr),
                                 std::shared_ptr<VsyncSchedule>(
@@ -400,7 +396,8 @@
 } // namespace surfaceflinger::test
 
 // TODO(b/189053744) : Create a common test/mock library for surfaceflinger
-class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback,
+                                     private scheduler::IVsyncTrackerCallback {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
@@ -609,9 +606,16 @@
             mFlinger->commitTransactions();
             mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp));
 
-            scheduler::FrameTargeter frameTargeter(displayId, mFdp.ConsumeBool());
-            mFlinger->postComposition(displayId, ftl::init::map(displayId, &frameTargeter),
-                                      mFdp.ConsumeIntegral<nsecs_t>());
+            scheduler::FeatureFlags flags;
+            if (mFdp.ConsumeBool()) {
+                flags |= scheduler::Feature::kBackpressureGpuComposition;
+            }
+            if (mFdp.ConsumeBool()) {
+                flags |= scheduler::Feature::kExpectedPresentTime;
+            }
+            scheduler::FrameTargeter frameTargeter(displayId, flags);
+            mFlinger->onCompositionPresented(displayId, ftl::init::map(displayId, &frameTargeter),
+                                             mFdp.ConsumeIntegral<nsecs_t>());
         }
 
         mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
@@ -656,6 +660,7 @@
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
                         scheduler::ISchedulerCallback* callback = nullptr,
+                        scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr,
                         bool hasMultipleModes = false) {
         constexpr DisplayModeId kModeId60{0};
         DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -666,19 +671,12 @@
         }
 
         mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
-        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate();
-        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
-
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
-                                                              hal::PowerMode::OFF);
-
-        auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
-                mFlinger->mVsyncConfiguration->getCurrentConfigs());
 
         mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                       std::move(vsyncTracker), mRefreshRateSelector,
-                                                      std::move(modulatorPtr), *(callback ?: this));
+                                                      mFactory, *mFlinger->mTimeStats,
+                                                      *(callback ?: this),
+                                                      *(vsyncTrackerCallback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -799,6 +797,9 @@
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
 
+    // IVsyncTrackerCallback overrides
+    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
             sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 39a7ee5..7aae3c4 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -133,7 +133,7 @@
                             ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
 
     layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
-    layer->onPostComposition(nullptr, fenceTime, fenceTime, compositorTiming);
+    layer->onCompositionPresented(nullptr, fenceTime, fenceTime, compositorTiming);
 
     layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
     layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index a8727f9..8a5500f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -24,10 +24,12 @@
 
 #include "Scheduler/OneShotTimer.h"
 #include "Scheduler/RefreshRateSelector.h"
+#include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/VSyncDispatchTimerQueue.h"
 #include "Scheduler/VSyncPredictor.h"
 #include "Scheduler/VSyncReactor.h"
 
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 
@@ -133,13 +135,13 @@
                 dispatch->schedule(tmp,
                                    {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
             },
             "o.o");
     dispatch->schedule(tmp,
                        {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                         .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                        .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                        .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
     dispatch->unregisterCallback(tmp);
     dispatch->cancel(tmp);
 }
@@ -161,33 +163,41 @@
     entry.update(*stubTracker, 0);
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()},
                    *stubTracker, 0);
     entry.disarm();
     entry.ensureNotRunning();
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()},
                    *stubTracker, 0);
     auto const wakeup = entry.wakeupTime();
     auto const ready = entry.readyTime();
     entry.callback(entry.executing(), *wakeup, *ready);
     entry.addPendingWorkloadUpdate({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
-                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+                                    .lastVsync = mFdp.ConsumeIntegral<nsecs_t>()});
     dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
 }
 
+struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
+    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+};
+
 void SchedulerFuzzer::fuzzVSyncPredictor() {
     uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
     uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
-    scheduler::VSyncPredictor tracker{kDisplayId, idealPeriod, historySize,
-                                      minimumSamplesForPrediction,
-                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
+    VsyncTrackerCallback callback;
+    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;
@@ -262,7 +272,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);
@@ -413,7 +426,15 @@
 }
 
 void SchedulerFuzzer::fuzzFrameTargeter() {
-    scheduler::FrameTargeter frameTargeter(kDisplayId, mFdp.ConsumeBool());
+    scheduler::FeatureFlags flags;
+    if (mFdp.ConsumeBool()) {
+        flags |= scheduler::Feature::kBackpressureGpuComposition;
+    }
+    if (mFdp.ConsumeBool()) {
+        flags |= scheduler::Feature::kExpectedPresentTime;
+    }
+
+    scheduler::FrameTargeter frameTargeter(kDisplayId, flags);
 
     const struct VsyncSource final : scheduler::IVsyncSource {
         explicit VsyncSource(FuzzedDataProvider& fuzzer) : fuzzer(fuzzer) {}
@@ -421,6 +442,7 @@
 
         Period period() const { return getFuzzedDuration(fuzzer); }
         TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+        Period minFramePeriod() const { return period(); }
     } vsyncSource{mFdp};
 
     int i = 10;
@@ -428,7 +450,8 @@
         frameTargeter.beginFrame({.frameBeginTime = getFuzzedTimePoint(mFdp),
                                   .vsyncId = getFuzzedVsyncId(mFdp),
                                   .expectedVsyncTime = getFuzzedTimePoint(mFdp),
-                                  .sfWorkDuration = getFuzzedDuration(mFdp)},
+                                  .sfWorkDuration = getFuzzedDuration(mFdp),
+                                  .hwcMinWorkDuration = getFuzzedDuration(mFdp)},
                                  vsyncSource);
 
         frameTargeter.setPresentFence(makeFakeFence());
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index 8061a8f..114f3b0 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -86,11 +86,13 @@
 
     bool addVsyncTimestamp(nsecs_t /* timestamp */) override { return true; }
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */,
+                                         std::optional<nsecs_t>) 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 +102,7 @@
         return true;
     }
 
-    void setRenderRate(Fps) override {}
+    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) override {}
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
@@ -109,6 +111,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/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 9599452..6c8972f 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -29,6 +29,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
+#include <common/FlagManager.h>
 #include <configstore/Utils.h>
 #include <displayservice/DisplayService.h>
 #include <errno.h>
@@ -149,7 +150,7 @@
 
     // publish gui::ISurfaceComposer, the new AIDL interface
     sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
-    if (flags::misc1()) {
+    if (FlagManager::getInstance().misc1()) {
         composerAIDL->setMinSchedulerPolicy(SCHED_FIFO, newPriority);
     }
     sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false,
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 19d194f..79379fe 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -32,6 +32,14 @@
 }
 
 flag {
+  name: "enable_layer_command_batching"
+  namespace: "core_graphics"
+  description: "This flag controls batching on createLayer/destroyLayer command with executeCommand."
+  bug: "290685621"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "dont_skip_on_early"
   namespace: "core_graphics"
   description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
@@ -52,4 +60,93 @@
   description: "Feature flag for SmallAreaDetection"
   bug: "283055450"
   is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+  name: "hotplug2"
+  namespace: "core_graphics"
+  description: "Feature flag for using hotplug2 HAL API"
+  bug: "303460805"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "hdcp_level_hal"
+  namespace: "core_graphics"
+  description: "Feature flag for adding a HAL API to commuicate hdcp levels"
+  bug: "285359126"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "add_sf_skipped_frames_to_trace"
+  namespace: "core_graphics"
+  description: "Add SurfaceFlinger dropped Frames to frame timeline"
+  bug: "273701290"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "refresh_rate_overlay_on_external_display"
+  namespace: "core_graphics"
+  description: "enable refresh rate indicator on the external display"
+  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"
+  is_fixed_read_only: true
+}
+
+# This flag is broken.
+# See alternative one: cache_when_source_crop_layer_only_moved
+# 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: "cache_when_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
+}
+
+flag {
+  name: "game_default_frame_rate"
+  namespace: "game"
+  description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
+  bug: "286084594"
+  is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 5888a55..5449aeb 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -55,7 +55,6 @@
         "ReleaseBufferCallback_test.cpp",
         "ScreenCapture_test.cpp",
         "SetFrameRate_test.cpp",
-        "SetFrameRateOverride_test.cpp",
         "SetGeometry_test.cpp",
         "Stress_test.cpp",
         "TextureFiltering_test.cpp",
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/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index 9269e7c..c9af432 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -47,7 +47,6 @@
         ASSERT_NO_FATAL_FAILURE(SetUpDisplay());
 
         sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-        mCaptureArgs.displayToken = mDisplay;
     }
 
     virtual void TearDown() {
@@ -279,8 +278,6 @@
     const int32_t mLayerZBase = std::numeric_limits<int32_t>::max() - 256;
 
     sp<SurfaceControl> mBlackBgSurface;
-
-    DisplayCaptureArgs mCaptureArgs;
     ScreenCaptureResults mCaptureResults;
 
 private:
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 96cc333..18262f6 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -15,6 +15,8 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <sys/types.h>
+#include <cstdint>
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -31,26 +33,22 @@
         LayerTransactionTest::SetUp();
         ASSERT_EQ(NO_ERROR, mClient->initCheck());
 
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        ASSERT_FALSE(ids.empty());
-        mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        ASSERT_FALSE(mDisplayToken == nullptr);
-
-        ui::DisplayMode mode;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode));
-        const ui::Size& resolution = mode.resolution;
-
-        mDisplaySize = resolution;
+        // Root surface
+        mRootSurfaceControl =
+                createLayer(String8("RootTestSurface"), mDisplayWidth, mDisplayHeight, 0);
+        ASSERT_TRUE(mRootSurfaceControl != nullptr);
+        ASSERT_TRUE(mRootSurfaceControl->isValid());
 
         // Background surface
-        mBGSurfaceControl = createLayer(String8("BG Test Surface"), resolution.getWidth(),
-                                        resolution.getHeight(), 0);
+        mBGSurfaceControl = createLayer(String8("BG Test Surface"), mDisplayWidth, mDisplayHeight,
+                                        0, mRootSurfaceControl.get());
         ASSERT_TRUE(mBGSurfaceControl != nullptr);
         ASSERT_TRUE(mBGSurfaceControl->isValid());
         TransactionUtils::fillSurfaceRGBA8(mBGSurfaceControl, 63, 63, 195);
 
         // Foreground surface
-        mFGSurfaceControl = createLayer(String8("FG Test Surface"), 64, 64, 0);
+        mFGSurfaceControl =
+                createLayer(String8("FG Test Surface"), 64, 64, 0, mRootSurfaceControl.get());
 
         ASSERT_TRUE(mFGSurfaceControl != nullptr);
         ASSERT_TRUE(mFGSurfaceControl->isValid());
@@ -58,7 +56,7 @@
         TransactionUtils::fillSurfaceRGBA8(mFGSurfaceControl, 195, 63, 63);
 
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK);
+            t.setDisplayLayerStack(mDisplay, ui::DEFAULT_LAYER_STACK);
 
             t.setLayer(mBGSurfaceControl, INT32_MAX - 2).show(mBGSurfaceControl);
 
@@ -66,25 +64,22 @@
                     .setPosition(mFGSurfaceControl, 64, 64)
                     .show(mFGSurfaceControl);
         });
+
+        mCaptureArgs.sourceCrop = mDisplayRect;
+        mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
     }
 
     virtual void TearDown() {
         LayerTransactionTest::TearDown();
         mBGSurfaceControl = 0;
         mFGSurfaceControl = 0;
-
-        // Restore display rotation
-        asTransaction([&](Transaction& t) {
-            Rect displayBounds{mDisplaySize};
-            t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, displayBounds, displayBounds);
-        });
     }
 
+    sp<SurfaceControl> mRootSurfaceControl;
     sp<SurfaceControl> mBGSurfaceControl;
     sp<SurfaceControl> mFGSurfaceControl;
     std::unique_ptr<ScreenCapture> mCapture;
-    sp<IBinder> mDisplayToken;
-    ui::Size mDisplaySize;
+    LayerCaptureArgs mCaptureArgs;
 };
 
 TEST_F(ScreenCaptureTest, SetFlagsSecureEUidSystem) {
@@ -92,7 +87,8 @@
     ASSERT_NO_FATAL_FAILURE(
             layer = createLayer("test", 32, 32,
                                 ISurfaceComposerClient::eSecure |
-                                        ISurfaceComposerClient::eFXSurfaceBufferQueue));
+                                        ISurfaceComposerClient::eFXSurfaceBufferQueue,
+                                mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
 
     Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
@@ -100,30 +96,45 @@
     {
         // Ensure the UID is not root because root has all permissions
         UIDFaker f(AID_APP_START);
-        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(mCaptureArgs, mCaptureResults));
+        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::captureDisplay(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.
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
-    args.captureSecureLayers = true;
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, 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) {
@@ -131,7 +142,8 @@
     ASSERT_NO_FATAL_FAILURE(
             parentLayer = createLayer("parent-test", 32, 32,
                                       ISurfaceComposerClient::eSecure |
-                                              ISurfaceComposerClient::eFXSurfaceBufferQueue));
+                                              ISurfaceComposerClient::eFXSurfaceBufferQueue,
+                                      mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(parentLayer, Color::RED, 32, 32));
 
     sp<SurfaceControl> childLayer;
@@ -152,15 +164,140 @@
 
     // 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.
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
-    args.captureSecureLayers = true;
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, mCaptureResults));
+    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, 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();
@@ -232,7 +369,7 @@
 
 TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) {
     mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
-    ScreenCapture::captureDisplay(&mCapture, mCaptureArgs);
+    ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
     mCapture->expectBGColor(0, 0);
     // Doesn't capture FG layer which is at 64, 64
     mCapture->expectBGColor(64, 64);
@@ -605,60 +742,55 @@
     mCapture->expectColor(Rect(30, 30, 60, 60), Color::RED);
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayWithUid) {
-    uid_t fakeUid = 12345;
+TEST_F(ScreenCaptureTest, ScreenshotProtectedBuffer) {
+    const uint32_t bufferWidth = 60;
+    const uint32_t bufferHeight = 60;
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
+    sp<SurfaceControl> layer =
+            createLayer(String8("Colored surface"), bufferWidth, bufferHeight,
+                        ISurfaceComposerClient::eFXSurfaceBufferState, mRootSurfaceControl.get());
 
-    sp<SurfaceControl> layer;
-    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
-                                                ISurfaceComposerClient::eFXSurfaceBufferQueue,
-                                                mBGSurfaceControl.get()));
-    ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
+    Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
 
-    Transaction().show(layer).setLayer(layer, INT32_MAX).apply();
+    sp<Surface> surface = layer->getSurface();
+    ASSERT_TRUE(surface != nullptr);
+    sp<ANativeWindow> anw(surface);
 
-    // Make sure red layer with the background layer is screenshot.
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
-    mCapture->expectBorder(Rect(0, 0, 32, 32), {63, 63, 195, 255});
+    ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), GRALLOC_USAGE_PROTECTED));
 
-    // From non system uid, can't request screenshot without a specified uid.
-    UIDFaker f(fakeUid);
-    ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(captureArgs, mCaptureResults));
+    int fenceFd;
+    ANativeWindowBuffer* buf = nullptr;
 
-    // Make screenshot request with current uid set. No layers were created with the current
-    // uid so screenshot will be black.
-    captureArgs.uid = fakeUid;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
-    mCapture->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+    // End test if device does not support USAGE_PROTECTED
+    // b/309965549 This check does not exit the test when running on AVDs
+    status_t err = anw->dequeueBuffer(anw.get(), &buf, &fenceFd);
+    if (err) {
+        return;
+    }
+    anw->queueBuffer(anw.get(), buf, fenceFd);
 
-    sp<SurfaceControl> layerWithFakeUid;
-    // Create a new layer with the current uid
-    ASSERT_NO_FATAL_FAILURE(layerWithFakeUid =
-                                    createLayer("new test layer", 32, 32,
-                                                ISurfaceComposerClient::eFXSurfaceBufferQueue,
-                                                mBGSurfaceControl.get()));
-    ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layerWithFakeUid, Color::GREEN, 32, 32));
-    Transaction()
-            .show(layerWithFakeUid)
-            .setLayer(layerWithFakeUid, INT32_MAX)
-            .setPosition(layerWithFakeUid, 128, 128)
-            .apply();
+    // USAGE_PROTECTED buffer is read as a black screen
+    ScreenCaptureResults captureResults;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
 
-    // Screenshot from the fakeUid caller with the uid requested allows the layer
-    // with that uid to be screenshotted. Everything else is black
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(128, 128, 160, 160), Color::GREEN);
-    mCapture->expectBorder(Rect(128, 128, 160, 160), Color::BLACK);
+    ScreenCapture sc(captureResults.buffer, captureResults.capturedHdrLayers);
+    sc.expectColor(Rect(0, 0, bufferWidth, bufferHeight), Color::BLACK);
+
+    // Reading color data will expectedly result in crash, only check usage bit
+    // b/309965549 Checking that the usage bit is protected does not work for
+    // devices that do not support usage protected.
+    mCaptureArgs.allowProtected = true;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
+    // ASSERT_EQ(GRALLOC_USAGE_PROTECTED, GRALLOC_USAGE_PROTECTED &
+    // captureResults.buffer->getUsage());
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayer) {
     sp<SurfaceControl> layer;
-    ASSERT_NO_FATAL_FAILURE(
-            layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+                                                ISurfaceComposerClient::eFXSurfaceEffect,
+                                                mRootSurfaceControl.get()));
 
     const Color layerColor = Color::RED;
     const Rect bounds = Rect(10, 10, 40, 40);
@@ -666,17 +798,13 @@
     Transaction()
             .show(layer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setCrop(layer, bounds)
             .apply();
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-
     {
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(bounds, layerColor);
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
     }
@@ -689,17 +817,18 @@
     {
         // Can't screenshot test layer since it now has flag
         // eLayerSkipScreenshot
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(bounds, {63, 63, 195, 255});
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
     }
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayerChild) {
     sp<SurfaceControl> layer;
     sp<SurfaceControl> childLayer;
-    ASSERT_NO_FATAL_FAILURE(
-            layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+                                                ISurfaceComposerClient::eFXSurfaceEffect,
+                                                mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("test layer", 0, 0,
                                                      ISurfaceComposerClient::eFXSurfaceEffect,
                                                      layer.get()));
@@ -713,7 +842,6 @@
             .show(layer)
             .show(childLayer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setColor(childLayer, {childColor.r / 255, childColor.g / 255, childColor.b / 255})
@@ -721,11 +849,8 @@
             .setCrop(childLayer, childBounds)
             .apply();
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-
     {
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(childBounds, childColor);
         mCapture->expectBorder(childBounds, layerColor);
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -739,7 +864,7 @@
     {
         // Can't screenshot child layer since the parent has the flag
         // eLayerSkipScreenshot
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(childBounds, {63, 63, 195, 255});
         mCapture->expectBorder(childBounds, {63, 63, 195, 255});
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -860,14 +985,10 @@
 
     Transaction().show(layer).hide(mFGSurfaceControl).reparent(layer, nullptr).apply();
 
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplay;
-
     {
         // Validate that the red layer is not on screen
-        ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-        mCapture->expectColor(Rect(0, 0, mDisplaySize.width, mDisplaySize.height),
-                              {63, 63, 195, 255});
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
+        mCapture->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), {63, 63, 195, 255});
     }
 
     LayerCaptureArgs captureArgs;
@@ -878,42 +999,6 @@
     mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayWith90DegRotation) {
-    asTransaction([&](Transaction& t) {
-        Rect newDisplayBounds{mDisplaySize.height, mDisplaySize.width};
-        t.setDisplayProjection(mDisplayToken, ui::ROTATION_90, newDisplayBounds, newDisplayBounds);
-    });
-
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplayToken;
-    displayCaptureArgs.width = mDisplaySize.width;
-    displayCaptureArgs.height = mDisplaySize.height;
-    displayCaptureArgs.useIdentityTransform = true;
-    ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-
-    mCapture->expectBGColor(0, 0);
-    mCapture->expectFGColor(mDisplaySize.width - 65, 65);
-}
-
-TEST_F(ScreenCaptureTest, CaptureDisplayWith270DegRotation) {
-    asTransaction([&](Transaction& t) {
-        Rect newDisplayBounds{mDisplaySize.height, mDisplaySize.width};
-        t.setDisplayProjection(mDisplayToken, ui::ROTATION_270, newDisplayBounds, newDisplayBounds);
-    });
-
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplayToken;
-    displayCaptureArgs.width = mDisplaySize.width;
-    displayCaptureArgs.height = mDisplaySize.height;
-    displayCaptureArgs.useIdentityTransform = true;
-    ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-
-    std::this_thread::sleep_for(std::chrono::seconds{5});
-
-    mCapture->expectBGColor(mDisplayWidth - 1, mDisplaySize.height - 1);
-    mCapture->expectFGColor(65, mDisplaySize.height - 65);
-}
-
 TEST_F(ScreenCaptureTest, CaptureNonHdrLayer) {
     sp<SurfaceControl> layer;
     ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
diff --git a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp
deleted file mode 100644
index e43ef95..0000000
--- a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp
+++ /dev/null
@@ -1,100 +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 <android/gui/ISurfaceComposer.h>
-#include <gtest/gtest.h>
-#include <gui/DisplayEventReceiver.h>
-#include <gui/SurfaceComposerClient.h>
-#include <sys/epoll.h>
-#include <algorithm>
-
-namespace android {
-namespace {
-using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
-using gui::ISurfaceComposer;
-
-class SetFrameRateOverrideTest : public ::testing::Test {
-protected:
-    void SetUp() override {
-        const ISurfaceComposer::VsyncSource vsyncSource =
-                ISurfaceComposer::VsyncSource::eVsyncSourceApp;
-        const EventRegistrationFlags eventRegistration = {
-                ISurfaceComposer::EventRegistration::frameRateOverride};
-
-        mDisplayEventReceiver =
-                std::make_unique<DisplayEventReceiver>(vsyncSource, eventRegistration);
-        EXPECT_EQ(NO_ERROR, mDisplayEventReceiver->initCheck());
-
-        mEpollFd = epoll_create1(EPOLL_CLOEXEC);
-        EXPECT_GT(mEpollFd, 1);
-
-        epoll_event event;
-        event.events = EPOLLIN;
-        EXPECT_EQ(0, epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mDisplayEventReceiver->getFd(), &event));
-    }
-
-    void TearDown() override { close(mEpollFd); }
-
-    void setFrameRateAndListenEvents(uid_t uid, float frameRate) {
-        status_t ret = SurfaceComposerClient::setOverrideFrameRate(uid, frameRate);
-        ASSERT_EQ(NO_ERROR, ret);
-
-        DisplayEventReceiver::Event event;
-        bool isOverrideFlushReceived = false;
-        mFrameRateOverrides.clear();
-
-        epoll_event epollEvent;
-        while (epoll_wait(mEpollFd, &epollEvent, 1, 1000) > 0) {
-            while (mDisplayEventReceiver->getEvents(&event, 1) > 0) {
-                if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE) {
-                    mFrameRateOverrides.emplace_back(event.frameRateOverride);
-                }
-                if (event.header.type ==
-                    DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH) {
-                    isOverrideFlushReceived = true;
-                }
-            }
-
-            if (isOverrideFlushReceived) break;
-        }
-    }
-
-    std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
-    std::vector<FrameRateOverride> mFrameRateOverrides;
-
-    int mEpollFd;
-};
-
-TEST_F(SetFrameRateOverrideTest, SetFrameRateOverrideCall) {
-    uid_t uid = getuid();
-    float frameRate = 30.0f;
-    setFrameRateAndListenEvents(uid, frameRate);
-    // check if the frame rate override we set exists
-    ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
-                             [uid = uid, frameRate = frameRate](auto i) {
-                                 return uid == i.uid && frameRate == i.frameRateHz;
-                             }) != mFrameRateOverrides.end());
-
-    // test removing frame rate override
-    frameRate = 0.0f;
-    setFrameRateAndListenEvents(uid, frameRate);
-    ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
-                             [uid = uid, frameRate = frameRate](auto i) {
-                                 return uid == i.uid && frameRate == i.frameRateHz;
-                             }) == mFrameRateOverrides.end());
-}
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
index d0ab105..c5d118c 100644
--- a/services/surfaceflinger/tests/TextureFiltering_test.cpp
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -80,29 +80,22 @@
     sp<SurfaceControl> mParent;
     sp<SurfaceControl> mLayer;
     std::unique_ptr<ScreenCapture> mCapture;
+    gui::LayerCaptureArgs captureArgs;
 };
 
 TEST_F(TextureFilteringTest, NoFiltering) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
-    captureArgs.sourceCrop = Rect{100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
     mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
 }
 
 TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
-    Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply();
-
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
     mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
@@ -112,24 +105,20 @@
 TEST_F(TextureFilteringTest, BufferCropIsFiltered) {
     Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
 
 // Expect filtering because the output source crop is stretched to the output buffer's size.
 TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
+    captureArgs.frameScaleX = 2;
+    captureArgs.frameScaleY = 2;
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
@@ -138,20 +127,17 @@
 // buffer's size.
 TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) {
     Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
-
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
+    captureArgs.frameScaleX = 2;
+    captureArgs.frameScaleY = 2;
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
 
 // Expect filtering because the layer is scaled up.
 TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.frameScaleX = 2;
     captureArgs.frameScaleY = 2;
@@ -162,7 +148,6 @@
 
 // Expect no filtering because the output buffer's size matches the source crop.
 TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) {
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
@@ -176,7 +161,6 @@
 TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) {
     Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply();
 
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
@@ -187,12 +171,9 @@
 
 // Expect no filtering because the output source crop and output buffer are the same size.
 TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 50;
-    captureArgs.height = 50;
+    captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
     mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
@@ -202,9 +183,8 @@
 TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) {
     Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mLayer->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
     mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
@@ -214,9 +194,8 @@
 TEST_F(TextureFilteringTest, ParentCropNoFiltering) {
     Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mLayer->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
     mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
@@ -226,7 +205,6 @@
 TEST_F(TextureFilteringTest, ParentHasTransformNoFiltering) {
     Transaction().setPosition(mParent, 100, 100).apply();
 
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mParent->getHandle();
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5a3bca1..5809ea0 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -45,7 +45,7 @@
 cc_aconfig_library {
     name: "libsurfaceflingerflags_test",
     aconfig_declarations: "surfaceflinger_flags",
-    test: true,
+    mode: "test",
 }
 
 cc_test {
@@ -106,6 +106,7 @@
         "SurfaceFlinger_HdrOutputControlTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
         "SurfaceFlinger_InitializeDisplaysTest.cpp",
+        "SurfaceFlinger_NotifyExpectedPresentTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
@@ -117,6 +118,7 @@
         "RefreshRateSelectorTest.cpp",
         "RefreshRateStatsTest.cpp",
         "RegionSamplingTest.cpp",
+        "TestableScheduler.cpp",
         "TimeStatsTest.cpp",
         "FrameTracerTest.cpp",
         "TransactionApplicationTest.cpp",
@@ -170,6 +172,7 @@
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
+        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 8e13c0d..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);
@@ -347,7 +346,7 @@
     }
 
     static void setupHwcCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
                 .Times(1);
 
         EXPECT_CALL(*test->mDisplaySurface,
@@ -356,12 +355,12 @@
     }
 
     static void setupHwcClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
                 .Times(1);
     }
 
     static void setupHwcForcedClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _)).Times(1);
+        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _, _)).Times(1);
     }
 
     static void setupRECompositionCallExpectations(CompositionTest* 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/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fa31643..1379665 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -80,7 +80,8 @@
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
+                            TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index ee12276..6671414 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -481,14 +481,17 @@
 
 constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
 
-template <typename PhysicalDisplay, int width, int height>
+template <typename PhysicalDisplay, int width, int height,
+          Secure secure = (PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal)
+                  ? Secure::TRUE
+                  : Secure::FALSE>
 struct PhysicalDisplayVariant
-      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE,
-                       Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
+      : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, secure,
+                       PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
                        PHYSICAL_DISPLAY_FLAGS>,
         HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
                           DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height,
-                                         Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
+                                         Async::FALSE, secure, PhysicalDisplay::PRIMARY,
                                          GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
                           PhysicalDisplay> {};
 
@@ -515,6 +518,7 @@
 };
 
 struct TertiaryDisplay {
+    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 253;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1003;
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 8891c06..45db0c5 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -55,6 +55,9 @@
 
 constexpr std::chrono::duration VSYNC_PERIOD(16ms);
 
+constexpr int HDCP_V1 = 2;
+constexpr int HDCP_V2 = 3;
+
 } // namespace
 
 class EventThreadTest : public testing::Test, public IEventThreadCallback {
@@ -471,7 +474,7 @@
 
     mock::VSyncTracker& mockTracker =
             *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker());
-    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
     VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
@@ -833,6 +836,19 @@
     expectVSyncCallbackScheduleReceived(true);
 }
 
+TEST_F(EventThreadTest, postHcpLevelsChanged) {
+    setupEventThread();
+
+    mThread->onHdcpLevelsChanged(EXTERNAL_DISPLAY_ID, HDCP_V1, HDCP_V2);
+    auto args = mConnectionEventCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE, event.header.type);
+    EXPECT_EQ(EXTERNAL_DISPLAY_ID, event.header.displayId);
+    EXPECT_EQ(HDCP_V1, event.hdcpLevelsChange.connectedLevel);
+    EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel);
+}
+
 } // namespace
 } // namespace android
 
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0905cd1..0c820fb 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -14,130 +14,174 @@
  * limitations under the License.
  */
 
-#include <cstdint>
 #undef LOG_TAG
 #define LOG_TAG "FlagManagerTest"
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
 
-#include <android-base/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
-#include <server_configurable_flags/get_flags.h>
-#include <optional>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
 
 namespace android {
 
 using testing::Return;
 
-class MockFlagManager : public FlagManager {
+class TestableFlagManager : public FlagManager {
 public:
-    MockFlagManager() = default;
-    ~MockFlagManager() = default;
+    TestableFlagManager() : FlagManager(ConstructorTag{}) { markBootCompleted(); }
+    ~TestableFlagManager() = default;
 
-    MOCK_METHOD(std::string, getServerConfigurableFlag, (const std::string& experimentFlagName),
-                (const, override));
+    MOCK_METHOD(std::optional<bool>, getBoolProperty, (const char*), (const, override));
+    MOCK_METHOD(bool, getServerConfigurableFlag, (const char*), (const, override));
+
+    void markBootIncomplete() { mBootCompleted = false; }
 };
 
 class FlagManagerTest : public testing::Test {
 public:
-    FlagManagerTest();
-    ~FlagManagerTest() override;
-    std::unique_ptr<MockFlagManager> mFlagManager;
+    FlagManagerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+    ~FlagManagerTest() override {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
 
-    template <typename T>
-    T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
-               T defaultValue);
+    TestableFlagManager mFlagManager;
 };
 
-FlagManagerTest::FlagManagerTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    mFlagManager = std::make_unique<MockFlagManager>();
+TEST_F(FlagManagerTest, isSingleton) {
+    EXPECT_EQ(&FlagManager::getInstance(), &FlagManager::getInstance());
 }
 
-FlagManagerTest::~FlagManagerTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+TEST_F(FlagManagerTest, legacyCreashesIfQueriedBeforeBoot) {
+    mFlagManager.markBootIncomplete();
+    EXPECT_DEATH(FlagManager::getInstance().test_flag(), "");
 }
 
-template <typename T>
-T FlagManagerTest::getValue(const std::string& experimentFlagName,
-                            std::optional<T> systemPropertyOpt, T defaultValue) {
-    return mFlagManager->getValue(experimentFlagName, systemPropertyOpt, defaultValue);
+TEST_F(FlagManagerTest, legacyReturnsOverride) {
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+    EXPECT_EQ(true, mFlagManager.test_flag());
+
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(false));
+    EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-namespace {
-TEST_F(FlagManagerTest, getValue_bool_default) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
-    const bool defaultValue = false;
-    std::optional<bool> systemPropertyValue = std::nullopt;
-    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, legacyReturnsValue) {
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_CALL(mFlagManager, getServerConfigurableFlag).WillOnce(Return(true));
+    EXPECT_EQ(true, mFlagManager.test_flag());
+
+    EXPECT_CALL(mFlagManager, getServerConfigurableFlag).WillOnce(Return(false));
+    EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-TEST_F(FlagManagerTest, getValue_bool_sysprop) {
-    const bool defaultValue = false;
-    std::optional<bool> systemPropertyValue = std::make_optional(true);
-    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, true);
+TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+    mFlagManager.markBootIncomplete();
+    EXPECT_DEATH(FlagManager::getInstance().late_boot_misc2(), "");
 }
 
-TEST_F(FlagManagerTest, getValue_bool_experiment) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("1"));
-    const bool defaultValue = false;
-    std::optional<bool> systemPropertyValue = std::nullopt;
-    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, true);
+TEST_F(FlagManagerTest, returnsOverrideTrue) {
+    mFlagManager.markBootCompleted();
+
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
+
+    // This is stored in a static variable, so this test depends on the fact
+    // that this flag has not been read in this process.
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+    EXPECT_TRUE(mFlagManager.late_boot_misc2());
+
+    // Further calls will not result in further calls to getBoolProperty.
+    EXPECT_TRUE(mFlagManager.late_boot_misc2());
 }
 
-TEST_F(FlagManagerTest, getValue_int32_default) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
-    int32_t defaultValue = 30;
-    std::optional<int32_t> systemPropertyValue = std::nullopt;
-    int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, returnsOverrideReadonly) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
+                      false);
+
+    // This is stored in a static variable, so this test depends on the fact
+    // that this flag has not been read in this process.
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+    EXPECT_TRUE(mFlagManager.add_sf_skipped_frames_to_trace());
 }
 
-TEST_F(FlagManagerTest, getValue_int32_sysprop) {
-    int32_t defaultValue = 30;
-    std::optional<int32_t> systemPropertyValue = std::make_optional(10);
-    int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, 10);
+TEST_F(FlagManagerTest, returnsOverrideFalse) {
+    mFlagManager.markBootCompleted();
+
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              refresh_rate_overlay_on_external_display,
+                      true);
+
+    // This is stored in a static variable, so this test depends on the fact
+    // that this flag has not been read in this process.
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(false));
+    EXPECT_FALSE(mFlagManager.refresh_rate_overlay_on_external_display());
 }
 
-TEST_F(FlagManagerTest, getValue_int32_experiment) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
-    std::int32_t defaultValue = 30;
-    std::optional<std::int32_t> systemPropertyValue = std::nullopt;
-    std::int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, 50);
+TEST_F(FlagManagerTest, ignoresOverrideInUnitTestMode) {
+    mFlagManager.setUnitTestMode();
+
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::multithreaded_present, true);
+
+    // If this has not been called in this process, it will be called.
+    // Regardless, the result is ignored.
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(false));
+
+    EXPECT_EQ(true, mFlagManager.multithreaded_present());
 }
 
-TEST_F(FlagManagerTest, getValue_int64_default) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
-    int64_t defaultValue = 30;
-    std::optional<int64_t> systemPropertyValue = std::nullopt;
-    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, returnsValue) {
+    mFlagManager.setUnitTestMode();
+
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+    {
+        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, true);
+        EXPECT_EQ(true, mFlagManager.late_boot_misc2());
+    }
+
+    {
+        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
+        EXPECT_EQ(false, mFlagManager.late_boot_misc2());
+    }
 }
 
-TEST_F(FlagManagerTest, getValue_int64_sysprop) {
-    int64_t defaultValue = 30;
-    std::optional<int64_t> systemPropertyValue = std::make_optional(10);
-    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, 10);
+TEST_F(FlagManagerTest, readonlyReturnsValue) {
+    mFlagManager.setUnitTestMode();
+
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+    {
+        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, true);
+        EXPECT_EQ(true, mFlagManager.misc1());
+    }
+
+    {
+        SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, false);
+        EXPECT_EQ(false, mFlagManager.misc1());
+    }
 }
 
-TEST_F(FlagManagerTest, getValue_int64_experiment) {
-    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
-    int64_t defaultValue = 30;
-    std::optional<int64_t> systemPropertyValue = std::nullopt;
-    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
-    ASSERT_EQ(result, 50);
+TEST_F(FlagManagerTest, dontSkipOnEarlyIsNotCached) {
+    EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+    const auto initialValue = com::android::graphics::surfaceflinger::flags::dont_skip_on_early();
+
+    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(true);
+    EXPECT_EQ(true, mFlagManager.dont_skip_on_early());
+
+    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(false);
+    EXPECT_EQ(false, mFlagManager.dont_skip_on_early());
+
+    com::android::graphics::surfaceflinger::flags::dont_skip_on_early(initialValue);
 }
-} // namespace
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
index a581c7a..7c1d4b4 100644
--- a/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
@@ -17,6 +17,8 @@
 #undef LOG_TAG
 #define LOG_TAG "FrameRateOverrideMappingsTest"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include <gtest/gtest.h>
 #include <unordered_map>
 
@@ -34,6 +36,8 @@
 };
 
 namespace {
+using namespace com::android::graphics::surfaceflinger;
+
 TEST_F(FrameRateOverrideMappingsTest, testUpdateFrameRateOverridesByContent) {
     mFrameRateOverrideByContent.clear();
     mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
@@ -59,6 +63,8 @@
 }
 
 TEST_F(FrameRateOverrideMappingsTest, testSetGameModeRefreshRateForUid) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, false);
+
     mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
     mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
 
@@ -95,6 +101,7 @@
 }
 
 TEST_F(FrameRateOverrideMappingsTest, testGetFrameRateOverrideForUidMixed) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, false);
     mFrameRateOverrideByContent.clear();
     mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
     mFrameRateOverrideByContent.emplace(1, 60.0_Hz);
@@ -111,7 +118,6 @@
     ASSERT_EQ(allFrameRateOverrides,
               mFrameRateOverrideMappings.getAllFrameRateOverrides(
                       /*supportsFrameRateOverrideByContent*/ true));
-
     mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
     mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
     mFrameRateOverrideMappings.setGameModeRefreshRateForUid({4, 120.0f});
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 636d852..ddc3967 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-
+#include <common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
 #include "gmock/gmock-spec-builders.h"
 #include "mock/MockTimeStats.h"
 #undef LOG_TAG
@@ -40,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 {
@@ -334,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) {
@@ -492,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
@@ -603,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) {
@@ -632,6 +639,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
@@ -661,6 +669,7 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
@@ -690,6 +699,7 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
@@ -720,6 +730,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
@@ -751,6 +762,7 @@
     mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+    EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
@@ -787,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);
 }
 
 /*
@@ -919,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);
@@ -928,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;
 }
@@ -948,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);
@@ -959,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;
@@ -1001,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());
 }
@@ -1048,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());
@@ -1059,6 +1081,98 @@
     EXPECT_EQ(received.cookie(), source.cookie());
 }
 
+TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
+                      true);
+
+    // setup 2 display frames
+    // DF 1: [22,40] -> [5, 40]
+    // DF  : [36, 70] (Skipped one, added by the trace)
+    // DF 2: [82, 100] -> SF [25, 70]
+    auto tracingSession = getTracingSessionForTest();
+    tracingSession->StartBlocking();
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 100});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
+    int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    int64_t traceCookie = snoopCurrentTraceCookie();
+
+    // set up 1st display frame
+    FrameTimelineInfo ftInfo1;
+    ftInfo1.vsyncId = surfaceFrameToken1;
+    ftInfo1.inputEventId = sInputEventId;
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo1, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    surfaceFrame1->setAcquireFenceTime(16);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_30);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->setSfPresent(30, presentFence1);
+    presentFence1->signalForTest(40);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    FrameTimelineInfo ftInfo2;
+    ftInfo2.vsyncId = surfaceFrameToken2;
+    ftInfo2.inputEventId = sInputEventId;
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    // set up 2nd display frame
+    surfaceFrame2->setAcquireFenceTime(36);
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_11, RR_30);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(90, presentFence2);
+    presentFence2->signalForTest(100);
+
+    // the token of skipped Display Frame
+    auto protoSkippedActualDisplayFrameStart =
+            createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid,
+                                               FrameTimelineEvent::PRESENT_DROPPED, true, false,
+                                               FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
+    auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    addEmptyDisplayFrame();
+    flushTrace();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // 8 Valid Display Frames + 8 Valid Surface Frames + 2 Skipped Display Frames
+    EXPECT_EQ(packets.size(), 18u);
+
+    // Packet - 16: Actual skipped Display Frame Start
+    // the timestamp should be equal to the 2nd expected surface frame's end time
+    const auto& packet16 = packets[16];
+    ASSERT_TRUE(packet16.has_timestamp());
+    EXPECT_EQ(packet16.timestamp(), 36u);
+    ASSERT_TRUE(packet16.has_frame_timeline_event());
+
+    const auto& event16 = packet16.frame_timeline_event();
+    const auto& actualSkippedDisplayFrameStart = event16.actual_display_frame_start();
+    validateTraceEvent(actualSkippedDisplayFrameStart, protoSkippedActualDisplayFrameStart);
+
+    // Packet - 17: Actual skipped Display Frame End
+    // the timestamp should be equal to the 2nd expected surface frame's present time
+    const auto& packet17 = packets[17];
+    ASSERT_TRUE(packet17.has_timestamp());
+    EXPECT_EQ(packet17.timestamp(), 70u);
+    ASSERT_TRUE(packet17.has_frame_timeline_event());
+
+    const auto& event17 = packet17.frame_timeline_event();
+    const auto& actualSkippedDisplayFrameEnd = event17.frame_end();
+    validateTraceEvent(actualSkippedDisplayFrameEnd, protoSkippedActualDisplayFrameEnd);
+}
+
 TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
     auto tracingSession = getTracingSessionForTest();
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -1084,6 +1198,7 @@
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
                                                FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::SEVERITY_NONE,
                                                FrameTimelineEvent::PREDICTION_VALID);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 2);
 
@@ -1163,6 +1278,7 @@
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
                                                false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::SEVERITY_UNKNOWN,
                                                FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
@@ -1238,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);
 
@@ -1250,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);
 
@@ -1396,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);
 
@@ -1473,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);
 
@@ -1551,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) {
@@ -1577,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);
@@ -1590,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) {
@@ -1616,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);
@@ -1630,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) {
@@ -1652,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) {
@@ -1697,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);
@@ -1711,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);
@@ -1725,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();
 
@@ -1733,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) {
@@ -1785,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);
@@ -1813,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) {
@@ -1871,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);
@@ -1899,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) {
@@ -1941,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) {
@@ -2003,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);
@@ -2023,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);
@@ -2030,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) {
@@ -2089,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);
@@ -2109,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) {
@@ -2174,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);
@@ -2194,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) {
@@ -2225,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);
@@ -2242,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) {
@@ -2272,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);
@@ -2279,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);
@@ -2286,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 4f545a9..a5c0657 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -21,6 +21,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <optional>
 #include <vector>
 
 // StrictMock<T> derives from T and is not marked final, so the destructor of T is expected to be
@@ -30,10 +31,12 @@
 #include <gmock/gmock.h>
 #pragma clang diagnostic pop
 
+#include <common/FlagManager.h>
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
 #include <chrono>
 
+#include <common/test/FlagUtils.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
@@ -41,6 +44,8 @@
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
@@ -53,11 +58,11 @@
 
 using Hwc2::Config;
 
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using hal::IComposerClient;
 using ::testing::_;
 using ::testing::DoAll;
-using ::testing::ElementsAreArray;
 using ::testing::Return;
 using ::testing::SetArgPointee;
 using ::testing::StrictMock;
@@ -79,11 +84,7 @@
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
 
-    void setDisplayData(HalDisplayId displayId, nsecs_t lastExpectedPresentTimestamp) {
-        ASSERT_TRUE(mHwc.mDisplayData.find(displayId) != mHwc.mDisplayData.end());
-        auto& displayData = mHwc.mDisplayData.at(displayId);
-        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
-    }
+    void setVrrTimeoutHint(bool status) { mHwc.mEnableVrrTimeout = status; }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -136,7 +137,7 @@
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
 
-    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
     {
         EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
@@ -214,18 +215,24 @@
     }
 }
 
-TEST_F(HWComposerTest, getModesWithDisplayConfigurations) {
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_OFF) {
+    // if vrr_config is off, getDisplayConfigurationsSupported() is off as well
+    // then getModesWithLegacyDisplayConfigs should be called instead
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, false);
+    ASSERT_FALSE(FlagManager::getInstance().vrr_config());
+
     constexpr hal::HWDisplayId kHwcDisplayId = 2;
     constexpr hal::HWConfigId kConfigId = 42;
     constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+
     expectHotplugConnect(kHwcDisplayId);
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
 
-    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
     {
-        EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
                 .WillOnce(Return(HalError::BAD_DISPLAY));
         EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
     }
@@ -234,13 +241,101 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
+                                        _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kWidth), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::HEIGHT, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kHeight), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::CONFIG_GROUP, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kConfigGroup), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::VSYNC_PERIOD, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kVsyncPeriod), Return(HalError::NONE)));
+
+        // Optional Parameters UNSUPPORTED
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{kConfigId}),
+                                      Return(HalError::NONE)));
+
+        auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().dpiX, -1);
+        EXPECT_EQ(modes.front().dpiY, -1);
+
+        // Optional parameters are supported
+        constexpr int32_t kDpi = 320;
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        // DPI values are scaled by 1000 in the legacy implementation.
+        EXPECT_EQ(modes.front().dpiX, kDpi / 1000.f);
+        EXPECT_EQ(modes.front().dpiY, kDpi / 1000.f);
+    }
+}
+
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+    ASSERT_TRUE(FlagManager::getInstance().vrr_config());
+
+    constexpr hal::HWDisplayId kHwcDisplayId = 2;
+    constexpr hal::HWConfigId kConfigId = 42;
+    constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+    expectHotplugConnect(kHwcDisplayId);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(true));
+
+    {
+        EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+                .WillOnce(Return(HalError::BAD_DISPLAY));
+        EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+    }
+    {
+        setVrrTimeoutHint(true);
+        constexpr int32_t kWidth = 480;
+        constexpr int32_t kHeight = 720;
+        constexpr int32_t kConfigGroup = 1;
+        constexpr int32_t kVsyncPeriod = 16666667;
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
-                                       NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs =
-                                                                           ms2ns(30),
-                                                                   .notifyExpectedPresentTimeoutNs =
-                                                                           ms2ns(30)}};
+                                       NotifyExpectedPresentConfig{.headsUpNs = ms2ns(30),
+                                                                   .timeoutNs = ms2ns(30)}};
         hal::DisplayConfiguration displayConfiguration{.configId = kConfigId,
                                                        .width = kWidth,
                                                        .height = kHeight,
@@ -270,9 +365,9 @@
         displayConfiguration.dpi = {kDpi, kDpi};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
-                .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
-                                        displayConfiguration}),
-                                Return(HalError::NONE)));
+                .WillRepeatedly(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+                                              displayConfiguration}),
+                                      Return(HalError::NONE)));
 
         modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
         EXPECT_EQ(modes.size(), size_t{1});
@@ -284,6 +379,10 @@
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
+
+        setVrrTimeoutHint(false);
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.front().vrrConfig->notifyExpectedPresentConfig, std::nullopt);
     }
 }
 
@@ -315,57 +414,9 @@
     EXPECT_FALSE(displayIdOpt);
 }
 
-TEST_F(HWComposerTest, notifyExpectedPresentTimeout) {
-    constexpr hal::HWDisplayId kHwcDisplayId = 2;
-    expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
-    ASSERT_TRUE(info);
-
-    auto expectedPresentTime = systemTime() + ms2ns(10);
-    const int32_t frameIntervalNs = static_cast<Fps>(60_Hz).getPeriodNsecs();
-    static constexpr nsecs_t kTimeoutNs = ms2ns(30);
-
-    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, /* lastExpectedPresentTimestamp= */ 0));
-
-    {
-        // Very first ExpectedPresent after idle, no previous timestamp
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is after the timeoutNs
-        expectedPresentTime += ms2ns(50);
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is after the last reported ExpectedPresent.
-        expectedPresentTime += ms2ns(10);
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
-        // representing we changed our decision and want to present earlier than previously
-        // reported.
-        expectedPresentTime -= ms2ns(20);
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, expectedPresentTime, frameIntervalNs,
-                                             kTimeoutNs);
-    }
-}
-
 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.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 95f1940..2b333f4 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -45,7 +45,8 @@
 
 // reparenting tests
 TEST_F(LayerHierarchyTest, addLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
     EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -64,7 +65,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, 11);
     reparentLayer(111, 12);
     reparentLayer(1221, 1);
@@ -79,7 +81,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
@@ -96,7 +99,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(1221, UNASSIGNED_LAYER_ID);
@@ -115,7 +119,8 @@
 }
 
 TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     destroyLayerHandle(111);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -139,7 +144,8 @@
 }
 
 TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     reparentLayer(11, 1);
 
@@ -154,7 +160,8 @@
 
 // offscreen tests
 TEST_F(LayerHierarchyTest, layerMovesOnscreen) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -170,7 +177,8 @@
 }
 
 TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -187,7 +195,8 @@
 
 // rel-z tests
 TEST_F(LayerHierarchyTest, setRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -200,7 +209,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -216,7 +226,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentToRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -231,7 +242,8 @@
 }
 
 TEST_F(LayerHierarchyTest, setParentAsRelativeParent) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -246,7 +258,8 @@
 }
 
 TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -262,7 +275,8 @@
 }
 
 TEST_F(LayerHierarchyTest, reparentRelativeLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(11, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -294,7 +308,8 @@
 
 // mirror tests
 TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -308,7 +323,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentLayer(11, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
@@ -324,7 +340,8 @@
 TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) {
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     createLayer(1111, 111);
     createLayer(112, 11);
@@ -340,7 +357,8 @@
 
 // mirror & relatives tests
 TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(111, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
 
@@ -371,7 +389,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 12);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12);
 
@@ -401,7 +420,8 @@
 }
 
 TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     reparentRelativeLayer(11, 2);
     reparentLayer(2, UNASSIGNED_LAYER_ID);
@@ -427,7 +447,8 @@
 }
 
 TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentRelativeLayer(1221, 2);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -462,7 +483,8 @@
 }
 
 TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     updateBackgroundColor(1, 0.5);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -485,7 +507,8 @@
     createLayer(11, 1);
     reparentLayer(1, 11);
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     std::vector<uint32_t> expectedTraversalPath = {};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -502,17 +525,11 @@
     createLayer(11, 1);
     reparentRelativeLayer(11, 2);
     reparentRelativeLayer(2, 11);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_EQ(invalidRelativeRoot, 11u);
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -534,16 +551,11 @@
     createLayer(221, 22);
     reparentRelativeLayer(22, 111);
     reparentRelativeLayer(11, 221);
-    mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
-
-    // fix loop
-    uint32_t invalidRelativeRoot;
-    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
-    EXPECT_TRUE(hasRelZLoop);
-    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
-    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
-    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+    LayerHierarchyBuilder hierarchyBuilder;
+    // this call is expected to fix the loop!
+    hierarchyBuilder.update(mLifecycleManager);
+    uint32_t unused;
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(unused));
 
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -554,7 +566,8 @@
 }
 
 TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(1, UNASSIGNED_LAYER_ID);
     UPDATE_AND_VERIFY(hierarchyBuilder);
 
@@ -568,7 +581,8 @@
 TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) {
     // remove default hierarchy
     mLifecycleManager = LayerLifecycleManager();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     createRootLayer(1);
     destroyLayerHandle(1);
     UPDATE_AND_VERIFY(hierarchyBuilder);
@@ -582,7 +596,8 @@
 // traversal path test
 TEST_F(LayerHierarchyTest, traversalPathId) {
     setZ(122, -1);
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     auto checkTraversalPathIdVisitor =
             [](const LayerHierarchy& hierarchy,
                const LayerHierarchy::TraversalPath& traversalPath) -> bool {
@@ -605,7 +620,8 @@
     createLayer(53, 5);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -639,7 +655,8 @@
     setZ(13, 1);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {1, 11, 13, 12};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -661,7 +678,8 @@
     setLayerStack(2, 10);
 
     mLifecycleManager.commitChanges();
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     UPDATE_AND_VERIFY(hierarchyBuilder);
     std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
@@ -672,7 +690,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -687,7 +706,8 @@
 }
 
 TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
     setLayerStack(3, 1);
@@ -701,7 +721,8 @@
 }
 
 TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -719,7 +740,8 @@
 }
 
 TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
     createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
     setLayerStack(3, 1);
@@ -737,7 +759,8 @@
 }
 
 TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
-    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
     reparentLayer(12, UNASSIGNED_LAYER_ID);
     mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
     UPDATE_AND_VERIFY(hierarchyBuilder);
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 48f8923..67e6249 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -176,14 +176,12 @@
     void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
 
     void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            hierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                    mLifecycleManager.getDestroyedLayers());
-        }
+        hierarchyBuilder.update(mLifecycleManager);
         mLifecycleManager.commitChanges();
 
         // rebuild layer hierarchy from scratch and verify that it matches the updated state.
-        LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+        LayerHierarchyBuilder newBuilder;
+        newBuilder.update(mLifecycleManager);
         EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
                   getTraversalPath(newBuilder.getHierarchy()));
         EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
@@ -478,6 +476,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;
 };
 
@@ -501,10 +510,7 @@
     }
 
     void update(LayerSnapshotBuilder& snapshotBuilder) {
-        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
-        }
+        mHierarchyBuilder.update(mLifecycleManager);
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
                                         .layerLifecycleManager = mLifecycleManager,
                                         .includeMetadata = false,
@@ -519,7 +525,7 @@
         mLifecycleManager.commitChanges();
     }
 
-    LayerHierarchyBuilder mHierarchyBuilder{{}};
+    LayerHierarchyBuilder mHierarchyBuilder;
 
     DisplayInfos mFrontEndDisplayInfos;
     bool mHasDisplayChanges = false;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 631adf1..734fddb 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -18,12 +18,14 @@
 #define LOG_TAG "LayerHistoryIntegrationTest"
 
 #include <Layer.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
 
 #include <renderengine/mock/FakeExternalTexture.h>
 
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "LayerHierarchyTest.h"
 #include "Scheduler/LayerHistory.h"
@@ -36,6 +38,7 @@
 namespace android::scheduler {
 
 using android::mock::createDisplayMode;
+using namespace com::android::graphics::surfaceflinger;
 
 class LayerHistoryIntegrationTest : public surfaceflinger::frontend::LayerSnapshotTestBase {
 protected:
@@ -53,7 +56,7 @@
     LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
         mFlinger.resetScheduler(mScheduler);
         mLifecycleManager = {};
-        mHierarchyBuilder = {{}};
+        mHierarchyBuilder = {};
     }
 
     void updateLayerSnapshotsAndLayerHistory(nsecs_t now) {
@@ -162,10 +165,12 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
 
     TestableSurfaceFlinger mFlinger;
+
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
 };
 
 namespace {
@@ -492,7 +497,9 @@
     EXPECT_EQ(1, frequentLayerCount(time));
 }
 
-TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayer) {
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayerIsActive) {
+    SET_FLAG_FOR_TEST(flags::misc1, false);
+
     auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
     auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
     hideLayer(2);
@@ -515,6 +522,31 @@
     EXPECT_EQ(2, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayerIsNotActive) {
+    SET_FLAG_FOR_TEST(flags::misc1, true);
+
+    auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+    auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+    hideLayer(2);
+    setFrameRate(1, 60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRate(2, 90.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    setBufferWithPresentTime(explicitVisiblelayer, time);
+    setBufferWithPresentTime(explicitInvisiblelayer, time);
+
+    EXPECT_EQ(2u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryIntegrationTest, infrequentAnimatingLayer) {
     auto layer = createLegacyAndFrontedEndLayer(1);
 
@@ -736,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();
@@ -747,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);
 
@@ -807,6 +854,81 @@
     ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
 }
 
+TEST_F(LayerHistoryIntegrationTest, hidingLayerUpdatesLayerHistory) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+
+    hideLayer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(0u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, showingLayerUpdatesLayerHistory) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    hideLayer(1);
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    showLayer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, updatingGeometryUpdatesWeight) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(100U /*width*/, 100U /*height*/, 1,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+    mFlinger.setLayerHistoryDisplayArea(100 * 100);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+
+    auto startingWeight = summary[0].weight;
+
+    setMatrix(1, 0.1f, 0.f, 0.f, 0.1f);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_GT(startingWeight, summary[0].weight);
+}
+
 class LayerHistoryIntegrationTestParameterized
       : public LayerHistoryIntegrationTest,
         public testing::WithParamInterface<std::chrono::nanoseconds> {};
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 33c1d86..9456e37 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -22,10 +22,12 @@
 #define LOG_TAG "LayerHistoryTest"
 
 #include <Layer.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
 
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
@@ -116,6 +118,9 @@
     auto createLayer(std::string name) {
         return sp<MockLayer>::make(mFlinger.flinger(), std::move(name));
     }
+    auto createLayer(std::string name, uint32_t uid) {
+        return sp<MockLayer>::make(mFlinger.flinger(), std::move(name), std::move(uid));
+    }
 
     void recordFramesAndExpect(const sp<MockLayer>& layer, nsecs_t& time, Fps frameRate,
                                Fps desiredRefreshRate, int numFrames) {
@@ -142,13 +147,16 @@
 
     mock::SchedulerCallback mSchedulerCallback;
 
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
-
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
 };
 
 namespace {
 
+using namespace com::android::graphics::surfaceflinger;
+
 TEST_F(LayerHistoryTest, singleLayerNoVoteDefaultCompatibility) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
@@ -241,6 +249,105 @@
     }
 }
 
+TEST_F(LayerHistoryTest, gameFrameRateOverrideMapping) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 60.0f}));
+
+    auto overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(0_Hz, overridePair.first);
+    EXPECT_EQ(60_Hz, overridePair.second);
+
+    history().updateGameModeFrameRateOverride(FrameRateOverride({0, 40.0f}));
+    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 120.0f}));
+
+    overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(40_Hz, overridePair.first);
+    EXPECT_EQ(60_Hz, overridePair.second);
+
+    overridePair = history().getGameFrameRateOverride(1);
+    EXPECT_EQ(120_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+
+    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 0.0f}));
+    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 0.0f}));
+
+    overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(40_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+
+    overridePair = history().getGameFrameRateOverride(1);
+    EXPECT_EQ(0_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+}
+
+TEST_F(LayerHistoryTest, oneLayerGameFrameRateOverride) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+    const uid_t uid = 0;
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const Fps gameModeFrameRate = Fps::fromValue(60.0f);
+    const auto layer = createLayer("GameFrameRateLayer", uid);
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    // update game default frame rate override
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    nsecs_t time = systemTime();
+    LayerHistory::Summary summary;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += gameDefaultFrameRate.getPeriodNsecs();
+
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(30.0_Hz, summary[0].desiredRefreshRate);
+
+    // test against setFrameRate vote
+    const Fps setFrameRate = Fps::fromValue(120.0f);
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(setFrameRate, Layer::FrameRateCompatibility::Default)));
+
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += setFrameRate.getPeriodNsecs();
+
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(120.0_Hz, summary[0].desiredRefreshRate);
+
+    // update game mode frame rate override
+    history().updateGameModeFrameRateOverride(
+            FrameRateOverride({uid, gameModeFrameRate.getValue()}));
+
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += gameModeFrameRate.getPeriodNsecs();
+
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(60.0_Hz, summary[0].desiredRefreshRate);
+}
+
 TEST_F(LayerHistoryTest, oneInvisibleLayer) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
@@ -555,6 +662,33 @@
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 }
 
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategoryNotVisibleDoesNotVote) {
+    SET_FLAG_FOR_TEST(flags::misc1, true);
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(12.34_Hz, Layer::FrameRateCompatibility::Default,
+                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Layer is not visible, so the layer is moved to inactive, infrequent, and it will not have
+    // votes to consider for refresh rate selection.
+    ASSERT_EQ(0, summarizeLayerHistory(time).size());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryTest, multipleLayers) {
     auto layer1 = createLayer("A");
     auto layer2 = createLayer("B");
@@ -780,6 +914,8 @@
 }
 
 TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
+    SET_FLAG_FOR_TEST(flags::misc1, false);
+
     auto explicitVisiblelayer = createLayer();
     auto explicitInvisiblelayer = createLayer();
 
@@ -810,6 +946,39 @@
     EXPECT_EQ(2, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, invisibleExplicitLayerDoesNotVote) {
+    SET_FLAG_FOR_TEST(flags::misc1, true);
+
+    auto explicitVisiblelayer = createLayer();
+    auto explicitInvisiblelayer = createLayer();
+
+    EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(
+                    Layer::FrameRate(60_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+    EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(
+                    Layer::FrameRate(90_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
+                     time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
+                     time, time, LayerHistory::LayerUpdateType::Buffer);
+
+    EXPECT_EQ(2, layerCount());
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
     auto layer = createLayer();
 
@@ -859,6 +1028,43 @@
     EXPECT_EQ(1, animatingLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, frontBufferedLayerVotesMax) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+    EXPECT_CALL(*layer, isFrontBuffered()).WillRepeatedly(Return(true));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
 TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) {
     auto layer = createLayer();
 
@@ -1064,6 +1270,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()));
@@ -1077,6 +1285,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 11072bc..22cfbd8 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -21,6 +21,7 @@
 
 #include <scheduler/Fps.h>
 
+#include <common/test/FlagUtils.h>
 #include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
@@ -28,6 +29,8 @@
 #include "TestableSurfaceFlinger.h"
 #include "mock/MockSchedulerCallback.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 namespace android::scheduler {
 
 using android::mock::createDisplayMode;
@@ -61,12 +64,16 @@
                                                                               HI_FPS)),
                                                   DisplayModeId(0));
     mock::SchedulerCallback mSchedulerCallback;
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler =
+            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
 };
 
 namespace {
 
+using namespace com::android::graphics::surfaceflinger;
+
 TEST_F(LayerInfoTest, prefersPresentTime) {
     std::deque<FrameTimeData> frameTimes;
     constexpr auto kExpectedFps = 50_Hz;
@@ -261,5 +268,18 @@
     ASSERT_EQ(actualVotes[0].fps, vote.fps);
 }
 
+TEST_F(LayerInfoTest, isFrontBuffered) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    ASSERT_FALSE(layerInfo.isFrontBuffered());
+
+    LayerProps prop = {.isFrontBuffered = true};
+    layerInfo.setLastPresentTime(0, 0, LayerHistory::LayerUpdateType::Buffer, true, prop);
+    ASSERT_TRUE(layerInfo.isFrontBuffered());
+
+    prop.isFrontBuffered = false;
+    layerInfo.setLastPresentTime(0, 0, LayerHistory::LayerUpdateType::Buffer, true, prop);
+    ASSERT_FALSE(layerInfo.isFrontBuffered());
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index e784eb7..55b20b3 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -58,8 +58,7 @@
 
     void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) {
         if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
-            mHierarchyBuilder.update(mLifecycleManager.getLayers(),
-                                     mLifecycleManager.getDestroyedLayers());
+            mHierarchyBuilder.update(mLifecycleManager);
         }
         args.root = mHierarchyBuilder.getHierarchy();
         actualBuilder.update(args);
@@ -651,7 +650,7 @@
     // │   └── 13
     // └── 2
     setFrameRate(11, 244.f, 0, 0);
-    setFrameRateCategory(122, 3 /* Normal */);
+    setFrameRateCategory(122, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL);
 
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     // verify parent 1 gets no vote
@@ -789,6 +788,231 @@
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
               scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set to default with strategy default)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(12, -1.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 0 /* Default */);
+    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_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              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::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::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::Propagate);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate category set to high with strategy OverrideChildren)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+    setFrameRate(122, 123.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+    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_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::High);
+    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.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate category to default with strategy default)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRateCategory(12, ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT);
+    setFrameRateSelectionStrategy(12, 0 /* Default */);
+    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})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              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::Propagate);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::Default);
+    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::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::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) {
@@ -898,4 +1122,64 @@
             gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
 }
 
+TEST_F(LayerSnapshotTest, isFrontBuffered) {
+    setBuffer(1,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(
+                      1U /*width*/, 1U /*height*/, 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888,
+                      GRALLOC_USAGE_HW_TEXTURE | AHARDWAREBUFFER_USAGE_FRONT_BUFFER /*usage*/));
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_TRUE(getSnapshot(1)->isFrontBuffered());
+
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                               1ULL /* bufferId */,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_HW_TEXTURE /*usage*/));
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    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/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 9aa089f..e9c4d80 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -91,7 +91,7 @@
 TEST_F(MessageQueueTest, commit) {
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
     EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
@@ -105,7 +105,7 @@
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -124,7 +124,7 @@
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                                  .readyDuration = 0,
-                                                                 .earliestVsync = 0};
+                                                                 .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -151,7 +151,7 @@
     const auto timingAfterCallback =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = kPresentTime.ns()};
+                                                     .lastVsync = kPresentTime.ns()};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
@@ -163,7 +163,7 @@
     const auto timing =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDifferentDuration.ns(),
                                                      .readyDuration = 0,
-                                                     .earliestVsync = 0};
+                                                     .lastVsync = 0};
 
     EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
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 faa12a1..1e526ba 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.
@@ -1537,12 +1607,89 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighHint) {
+    auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::HighHint;
+    lr2.name = "ExplicitCategory HighHint#2";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExact";
+    actualFrameRateMode = selector.getBestFrameRateMode(layers);
+    if (selector.supportsAppFrameRateOverrideByContent()) {
+        // Gets touch boost
+        EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+        EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
+    } else {
+        EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
+        EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+    }
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
     if (GetParam() != Config::FrameRateOverride::Enabled) {
         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);
 
@@ -1568,7 +1715,7 @@
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
@@ -1610,6 +1757,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 +1772,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 +3634,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 +3654,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 +3712,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 +3736,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 +3766,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 87fae2c..6986689 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;
@@ -89,14 +95,15 @@
                                                   kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mSchedulerCallback};
-    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+    mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    TestableSurfaceFlinger mFlinger;
+    TestableScheduler* mScheduler =
+            new TestableScheduler{mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback};
+    surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
 
     ConnectionHandle mConnectionHandle;
     MockEventThread* mEventThread;
     sp<MockEventThreadConnection> mEventThreadConnection;
-
-    TestableSurfaceFlinger mFlinger;
 };
 
 SchedulerTest::SchedulerTest() {
@@ -455,26 +462,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});
@@ -484,21 +516,175 @@
         }
 
         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,
+                                mFlinger.getFactory(),
+                                mFlinger.getTimeStats(),
+                                mSchedulerCallback,
+                                mVsyncTrackerCallback};
+
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+    vrrTracker->addVsyncTimestamp(0);
+
+    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(1000)));
+    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(2000)));
+
+    // Not crossing the min frame period
+    EXPECT_EQ(Fps::fromPeriodNsecs(1500),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(2500)));
+    // 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(2000)));
+    EXPECT_EQ(Fps::fromPeriodNsecs(2000),
+              scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+                                             TimePoint::fromNs(4000)));
+}
+
+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..8b16a8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -46,16 +46,20 @@
 
         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>();
         auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         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))
@@ -134,22 +138,25 @@
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
     EXPECT_CALL(*vsyncTracker, currentPeriod())
             .WillRepeatedly(
                     Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    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),
                             std::move(selectorPtr),
                             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_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index fc5f2b0..1583f64 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -46,7 +46,7 @@
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
-                nextAnticipatedVSyncTimeFrom(_))
+                nextAnticipatedVSyncTimeFrom(_, _))
             .WillRepeatedly(Return(0));
 
     // --------------------------------------------------------------------
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
new file mode 100644
index 0000000..7206e29
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+class NotifyExpectedPresentTest : public DisplayTransactionTest {
+public:
+    void SetUp() override {
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+                .setPowerMode(hal::PowerMode::ON)
+                .inject(&mFlinger, mComposer);
+    }
+
+protected:
+    sp<DisplayDevice> mDisplay;
+    static constexpr bool kIsPrimary = true;
+    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+};
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    static constexpr Fps kFps60Hz = 60_Hz;
+    static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
+    static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+    static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
+    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  kLastExpectedPresentTimestamp,
+                                                                  kFps60Hz));
+
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // Absent timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 /*timeoutOpt*/ std::nullopt);
+    }
+    {
+        // Timeout is 0
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+    }
+    {
+        // ExpectedPresent is after the timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent has not changed
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is after the last reported ExpectedPresent.
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
+        // representing we changed our decision and want to present earlier than previously
+        // reported.
+        expectedPresentTime -= kFrameInterval120HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    const auto now = systemTime();
+    auto expectedPresentTime = now;
+    static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  TimePoint::fromNs(now),
+                                                                  Fps::fromValue(0)));
+    static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+
+    struct FrameRateIntervalTestData {
+        int32_t frameIntervalNs;
+        bool callExpectedPresent;
+    };
+    const std::vector<FrameRateIntervalTestData> frameIntervals = {
+            {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs120Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
+            {kFrameIntervalNs24Hz, true},  {kFrameIntervalNs40Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
+    };
+
+    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
+        {
+            expectedPresentTime += frameIntervalNs;
+            if (callExpectedPresent) {
+                EXPECT_CALL(*mComposer,
+                            notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                                  frameIntervalNs))
+                        .WillOnce(Return(Error::NONE));
+            } else {
+                EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+            }
+            mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                     TimePoint::fromNs(expectedPresentTime),
+                                                     Fps::fromPeriodNsecs(frameIntervalNs),
+                                                     kTimeoutNs);
+        }
+    }
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index d0290ea..b80cb66 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -103,7 +103,7 @@
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
-    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _)).WillOnce([] {
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
@@ -124,7 +124,7 @@
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
-    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _)).WillOnce([] {
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
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.cpp b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
new file mode 100644
index 0000000..e0b7366
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android::scheduler {
+
+TestableScheduler::TestableScheduler(RefreshRateSelectorPtr selectorPtr,
+                                     TestableSurfaceFlinger& testableSurfaceFlinger,
+                                     ISchedulerCallback& callback,
+                                     IVsyncTrackerCallback& vsyncTrackerCallback)
+      : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
+                          std::make_shared<android::mock::VSyncTracker>(), std::move(selectorPtr),
+                          testableSurfaceFlinger.getFactory(),
+                          testableSurfaceFlinger.getTimeStats(), callback, vsyncTrackerCallback) {}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 3d1c900..6213713 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,22 +32,27 @@
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
+namespace android {
+class TestableSurfaceFlinger;
+} // namespace android
+
 namespace android::scheduler {
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
-          : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
-                              sp<VsyncModulator>::make(VsyncConfigSet{}), callback) {}
+    TestableScheduler(RefreshRateSelectorPtr selectorPtr,
+                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback);
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
-                      sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
-          : Scheduler(*this, callback,
+                      surfaceflinger::Factory& factory, TimeStats& timeStats,
+                      ISchedulerCallback& schedulerCallback,
+                      IVsyncTrackerCallback& vsyncTrackerCallback)
+          : Scheduler(*this, schedulerCallback,
                       (FeatureFlags)Feature::kContentDetection |
                               Feature::kSmallDirtyContentDetection,
-                      std::move(modulatorPtr)) {
+                      factory, selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
@@ -75,10 +80,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,
@@ -111,6 +117,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 8f1982d..f00eacc 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -53,6 +53,7 @@
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 #include "mock/MockSchedulerCallback.h"
+#include "mock/MockVsyncTrackerCallback.h"
 #include "mock/system/window/MockNativeWindow.h"
 
 #include "Scheduler/VSyncTracker.h"
@@ -204,6 +205,8 @@
 
     enum class SchedulerCallbackImpl { kNoOp, kMock };
 
+    enum class VsyncTrackerCallbackImpl { kNoOp, kMock };
+
     struct DefaultDisplayMode {
         // The ID of the injected RefreshRateSelector and its default display mode.
         PhysicalDisplayId displayId;
@@ -213,13 +216,18 @@
 
     using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>;
 
-    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
-                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
-                        std::unique_ptr<EventThread> appEventThread,
-                        std::unique_ptr<EventThread> sfEventThread,
-                        DisplayModesVariant modesVariant,
-                        SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
-                        bool useNiceMock = false) {
+    surfaceflinger::Factory& getFactory() { return mFactory; }
+
+    TimeStats& getTimeStats() { return *mFlinger->mTimeStats; }
+
+    void setupScheduler(
+            std::unique_ptr<scheduler::VsyncController> vsyncController,
+            std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
+            std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread,
+            DisplayModesVariant modesVariant,
+            SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
+            VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp,
+            bool useNiceMock = false) {
         RefreshRateSelectorPtr selectorPtr = ftl::match(
                 modesVariant,
                 [](DefaultDisplayMode arg) {
@@ -230,35 +238,34 @@
                 },
                 [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; });
 
-        const auto fps = selectorPtr->getActiveMode().fps;
-        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
-
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
-                                                              hal::PowerMode::OFF);
-
         mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
 
-        using Callback = scheduler::ISchedulerCallback;
-        Callback& callback = callbackImpl == SchedulerCallbackImpl::kNoOp
-                ? static_cast<Callback&>(mNoOpSchedulerCallback)
-                : static_cast<Callback&>(mSchedulerCallback);
+        using ISchedulerCallback = scheduler::ISchedulerCallback;
+        ISchedulerCallback& schedulerCallback = callbackImpl == SchedulerCallbackImpl::kNoOp
+                ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback)
+                : static_cast<ISchedulerCallback&>(mSchedulerCallback);
 
-        auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
-                mFlinger->mVsyncConfiguration->getCurrentConfigs());
+        using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback;
+        VsyncTrackerCallback& vsyncTrackerCallback =
+                vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp
+                ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback)
+                : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback);
 
         if (useNiceMock) {
             mScheduler =
                     new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
                                                                         std::move(vsyncTracker),
                                                                         std::move(selectorPtr),
-                                                                        std::move(modulatorPtr),
-                                                                        callback);
+                                                                        mFactory,
+                                                                        *mFlinger->mTimeStats,
+                                                                        schedulerCallback,
+                                                                        vsyncTrackerCallback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
-                                                          std::move(selectorPtr),
-                                                          std::move(modulatorPtr), callback);
+                                                          std::move(selectorPtr), mFactory,
+                                                          *mFlinger->mTimeStats, schedulerCallback,
+                                                          vsyncTrackerCallback);
         }
 
         mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
@@ -271,7 +278,7 @@
         resetScheduler(mScheduler);
     }
 
-    void setupMockScheduler(test::MockSchedulerOptions options = {}) {
+    void setupMockScheduler(surfaceflinger::test::MockSchedulerOptions options = {}) {
         using testing::_;
         using testing::Return;
 
@@ -291,13 +298,17 @@
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
-        EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+        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},
-                       SchedulerCallbackImpl::kNoOp, options.useNiceMock);
+                       SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
+                       options.useNiceMock);
     }
 
     void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
@@ -375,13 +386,14 @@
         LOG_ALWAYS_FATAL_IF(!displayIdOpt);
         const auto displayId = *displayIdOpt;
 
-        constexpr bool kBackpressureGpuComposition = true;
-        scheduler::FrameTargeter frameTargeter(displayId, kBackpressureGpuComposition);
+        scheduler::FrameTargeter frameTargeter(displayId,
+                                               scheduler::Feature::kBackpressureGpuComposition);
 
         frameTargeter.beginFrame({.frameBeginTime = frameTime,
                                   .vsyncId = vsyncId,
                                   .expectedVsyncTime = expectedVsyncTime,
-                                  .sfWorkDuration = 10ms},
+                                  .sfWorkDuration = 10ms,
+                                  .hwcMinWorkDuration = 10ms},
                                  *mScheduler->getVsyncSchedule());
 
         scheduler::FrameTargets targets;
@@ -450,8 +462,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) {
@@ -477,12 +489,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,
@@ -610,6 +623,9 @@
 
     void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
 
+    auto setLayerHistoryDisplayArea(uint32_t displayArea) {
+        return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
+    };
     auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
     auto setDaltonizerType(ColorBlindnessType type) {
         mFlinger->mDaltonizer.setType(type);
@@ -675,6 +691,21 @@
         mFlinger->mLegacyFrontEndEnabled = false;
     }
 
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt) {
+        mFlinger->notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
+                                                  frameInterval, timeoutOpt);
+    }
+
+    void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
+                                      TimePoint lastExpectedPresentTimestamp,
+                                      Fps lastFrameInterval) {
+        auto& displayData = mFlinger->mNotifyExpectedPresentMap[displayId];
+        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
+        displayData.lastFrameInterval = lastFrameInterval;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -1071,6 +1102,8 @@
     sp<SurfaceFlinger> mFlinger;
     scheduler::mock::SchedulerCallback mSchedulerCallback;
     scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
+    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
+    scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
     scheduler::TestableScheduler* mScheduler = nullptr;
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 00b5bf0..d4d5b32 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -112,7 +112,7 @@
         EXPECT_CALL(*mFlinger.getFrameTracer(),
                     traceFence(layerId, bufferId, frameNumber, presentFence,
                                FrameTracer::FrameEvent::PRESENT_FENCE, /*startTime*/ 0));
-        layer->onPostComposition(nullptr, glDoneFence, presentFence, compositorTiming);
+        layer->onCompositionPresented(nullptr, glDoneFence, presentFence, compositorTiming);
     }
 };
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
index 379135e..d071ce9 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -45,12 +45,21 @@
     TestableSurfaceFlinger mFlinger;
 };
 
-TEST_F(TransactionTraceWriterTest, canWriteToFile) {
+// Check that a new file is written if overwrite=true and no file exists.
+TEST_F(TransactionTraceWriterTest, canWriteToFile_overwriteTrue) {
     TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
     EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
     verifyTraceFile();
 }
 
+// Check that a new file is written if overwrite=false and no file exists.
+TEST_F(TransactionTraceWriterTest, canWriteToFile_overwriteFalse) {
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+    EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+    verifyTraceFile();
+}
+
+// Check that an existing file is overwritten when overwrite=true.
 TEST_F(TransactionTraceWriterTest, canOverwriteFile) {
     std::string testLine = "test";
     {
@@ -61,6 +70,7 @@
     verifyTraceFile();
 }
 
+// Check that an existing file isn't overwritten when it is new and overwrite=false.
 TEST_F(TransactionTraceWriterTest, doNotOverwriteFile) {
     std::string testLine = "test";
     {
@@ -76,4 +86,35 @@
         EXPECT_EQ(line, testLine);
     }
 }
+
+// Check that an existing file is overwritten when it is old and overwrite=false.
+TEST_F(TransactionTraceWriterTest, overwriteOldFile) {
+    std::string testLine = "test";
+    {
+        std::ofstream file(mFilename, std::ios::out);
+        file << testLine;
+    }
+
+    // Update file modification time to 15 minutes ago.
+    using Clock = std::filesystem::file_time_type::clock;
+    std::error_code error;
+    std::filesystem::last_write_time(mFilename, Clock::now() - std::chrono::minutes{15}, error);
+    ASSERT_EQ(error.value(), 0);
+
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+    verifyTraceFile();
+}
+
+// Check we cannot write to file if the trace write is disabled.
+TEST_F(TransactionTraceWriterTest, canDisableTraceWriter) {
+    TransactionTraceWriter::getInstance().disable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_NE(access(mFilename.c_str(), F_OK), 0);
+
+    TransactionTraceWriter::getInstance().enable();
+    TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+    EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+    verifyTraceFile();
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 7981224..fb4ef70 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -214,6 +214,7 @@
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 42);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).layer_id(), mChildLayerId);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
+    EXPECT_TRUE(proto.entry(0).displays_changed());
 }
 
 TEST_F(TransactionTracingLayerHandlingTest, updateStartingState) {
@@ -224,6 +225,7 @@
     perfetto::protos::TransactionTraceFile proto = writeToProto();
     // verify starting states are updated correctly
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 41);
+    EXPECT_TRUE(proto.entry(0).displays_changed());
 }
 
 TEST_F(TransactionTracingLayerHandlingTest, removeStartingState) {
@@ -235,6 +237,7 @@
     // verify the child layer has been removed from the trace
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+    EXPECT_TRUE(proto.entry(0).displays_changed());
 }
 
 TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) {
@@ -254,6 +257,7 @@
     // verify we still have the parent layer state
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
     EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+    EXPECT_TRUE(proto.entry(0).displays_changed());
 }
 
 class TransactionTracingMirrorLayerTest : public TransactionTracingTest {
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 41866a1..d891008 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -34,40 +34,50 @@
     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 nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
+    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, std::optional<nsecs_t>) const final {
         auto const floor = timePoint % mPeriod;
         if (floor == 0) {
             return timePoint;
         }
         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 setRenderRate(Fps) 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} {}
+    VRRStubTracker(nsecs_t period) : StubTracker(period) {}
 
-    bool addVsyncTimestamp(nsecs_t) final { return true; }
-
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) const final {
         std::lock_guard lock(mMutex);
         auto const normalized_to_base = time_point - mBase;
         auto const floor = (normalized_to_base) % mPeriod;
@@ -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 setRenderRate(Fps) final {}
-    void dump(std::string&) const final {}
-
 private:
-    std::mutex mutable mMutex;
-    nsecs_t mPeriod;
     nsecs_t mBase = 0;
 };
 
@@ -121,7 +117,7 @@
         mCallback.schedule(
                 {.workDuration = mWorkload,
                  .readyDuration = mReadyDuration,
-                 .earliestVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
+                 .lastVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
 
         for (auto i = 0u; i < iterations - 1; i++) {
             std::unique_lock lock(mMutex);
@@ -134,7 +130,7 @@
 
             mCallback.schedule({.workDuration = mWorkload,
                                 .readyDuration = mReadyDuration,
-                                .earliestVsync = last + mWorkload + mReadyDuration});
+                                .lastVsync = last + mWorkload + mReadyDuration});
         }
 
         // wait for the last callback.
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 1dc5498..4bf58de 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,27 +43,17 @@
 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(_))
+        ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_, _))
                 .WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime));
         ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true));
         ON_CALL(*this, currentPeriod())
                 .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, setRenderRate, (Fps), (override));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
-
-    nsecs_t nextVSyncTime(nsecs_t timePoint) const {
+    nsecs_t nextVSyncTime(nsecs_t timePoint, std::optional<nsecs_t>) const {
         if (timePoint % mPeriod == 0) {
             return timePoint;
         }
@@ -252,10 +243,9 @@
                                                           mDispatchGroupThreshold,
                                                           mVsyncMoveThreshold);
         CountingCallback cb(mDispatch);
-        const auto result = mDispatch->schedule(cb,
-                                                {.workDuration = 100,
-                                                 .readyDuration = 0,
-                                                 .earliestVsync = 1000});
+        const auto result =
+                mDispatch->schedule(cb,
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
         EXPECT_TRUE(result.has_value());
         EXPECT_EQ(900, *result);
     }
@@ -266,10 +256,9 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
@@ -286,16 +275,14 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    auto result = mDispatch->schedule(cb,
-                                      {.workDuration = 100,
-                                       .readyDuration = 0,
-                                       .earliestVsync = intended});
+    auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
     result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(700, *result);
 
@@ -312,17 +299,18 @@
 
     CountingCallback cb(mDispatch);
     const auto result =
-            mDispatch->update(cb,
-                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .lastVsync = intended});
     EXPECT_FALSE(result.has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(mPeriod)))
+            .WillOnce(Return(1150));
     EXPECT_CALL(mMockClock, alarmAt(_, 1050));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -333,7 +321,8 @@
     auto const now = 234;
     mMockClock.advanceBy(234);
     auto const workDuration = 10 * mPeriod;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + workDuration, std::optional<nsecs_t>(mPeriod)))
             .WillOnce(Return(mPeriod * 11));
     EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
 
@@ -341,7 +330,7 @@
     const auto result = mDispatch->schedule(cb,
                                             {.workDuration = workDuration,
                                              .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+                                             .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod, *result);
 }
@@ -351,10 +340,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
     EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
@@ -365,10 +353,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
     mMockClock.advanceBy(950);
@@ -380,10 +367,9 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
@@ -402,10 +388,9 @@
 
     PausingCallback cb(mDispatch, 50ms);
     cb.stashResource(resource);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 100,
-                                             .readyDuration = 0,
-                                             .earliestVsync = mPeriod});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
@@ -422,7 +407,8 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(4)
             .WillOnce(Return(1055))
             .WillOnce(Return(1063))
@@ -437,8 +423,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -450,7 +436,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(1000))
             .WillOnce(Return(2000))
@@ -464,21 +450,21 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 0});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
     EXPECT_THAT(cb.mCalls[0], Eq(1000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(2));
     EXPECT_THAT(cb.mCalls[1], Eq(2000));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 
     advanceToNextCallback();
 
@@ -487,7 +473,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(4)
             .WillOnce(Return(10000))
             .WillOnce(Return(1000))
@@ -502,9 +488,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0,
-                        {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
-    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = mPeriod * 10});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .lastVsync = mPeriod});
     mDispatch->cancel(cb1);
 }
 
@@ -516,9 +501,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -531,9 +516,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -551,10 +536,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1,
-                        {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = closeOffset, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     ASSERT_THAT(cb0.mCalls.size(), Eq(1));
@@ -562,11 +546,9 @@
     ASSERT_THAT(cb1.mCalls.size(), Eq(1));
     EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
 
-    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .lastVsync = 2000});
     mDispatch->schedule(cb1,
-                        {.workDuration = notCloseOffset,
-                         .readyDuration = 0,
-                         .earliestVsync = 2000});
+                        {.workDuration = notCloseOffset, .readyDuration = 0, .lastVsync = 2000});
     advanceToNextCallback();
     ASSERT_THAT(cb1.mCalls.size(), Eq(2));
     EXPECT_THAT(cb1.mCalls[1], Eq(2000));
@@ -586,32 +568,32 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(3)
             .WillOnce(Return(950))
             .WillOnce(Return(1975))
             .WillOnce(Return(2950));
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 920});
 
     mMockClock.advanceBy(850);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1900});
     mMockClock.advanceBy(900);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
     mMockClock.advanceBy(125);
     EXPECT_THAT(cb.mCalls.size(), Eq(2));
 
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2900});
     mMockClock.advanceBy(975);
     EXPECT_THAT(cb.mCalls.size(), Eq(3));
 }
@@ -625,13 +607,11 @@
     tmp = mDispatch->registerCallback(
             [&](auto, auto, auto) {
                 mDispatch->schedule(tmp,
-                                    {.workDuration = 100,
-                                     .readyDuration = 0,
-                                     .earliestVsync = 2000});
+                                    {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
             },
             "o.o");
 
-    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -640,30 +620,29 @@
     std::optional<nsecs_t> lastTarget;
     tmp = mDispatch->registerCallback(
             [&](auto timestamp, auto, auto) {
-                auto result =
-                        mDispatch->schedule(tmp,
-                                            {.workDuration = 400,
-                                             .readyDuration = 0,
-                                             .earliestVsync = timestamp - mVsyncMoveThreshold});
+                auto result = mDispatch->schedule(tmp,
+                                                  {.workDuration = 400,
+                                                   .readyDuration = 0,
+                                                   .lastVsync = timestamp - mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp});
+                                              .lastVsync = timestamp});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 result = mDispatch->schedule(tmp,
                                              {.workDuration = 400,
                                               .readyDuration = 0,
-                                              .earliestVsync = timestamp + mVsyncMoveThreshold});
+                                              .lastVsync = timestamp + mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 lastTarget = timestamp;
             },
             "oo");
 
-    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .lastVsync = 1000});
     advanceToNextCallback();
     EXPECT_THAT(lastTarget, Eq(1000));
 
@@ -679,16 +658,16 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .lastVsync = 1000});
 
     mMockClock.advanceBy(750);
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .lastVsync = 2000});
 
     mMockClock.advanceBy(800);
-    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
@@ -701,12 +680,12 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
-    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .lastVsync = 2000});
+    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -718,8 +697,8 @@
 
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .lastVsync = 20000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
@@ -729,17 +708,15 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
 
     CountingCallback cb0(mDispatch);
-    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     mDispatch->cancel(cb0);
-    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
     VSyncDispatch::CallbackToken token(100);
     EXPECT_FALSE(
-            mDispatch
-                    ->schedule(token,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+            mDispatch->schedule(token, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000})
                     .has_value());
     EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
 }
@@ -747,12 +724,10 @@
 TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -764,14 +739,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
 
@@ -783,17 +756,17 @@
 TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
     SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
 
-    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(400, *result);
 
@@ -802,19 +775,18 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1002));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(602, *result);
 }
@@ -822,13 +794,12 @@
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -839,13 +810,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch->schedule(cb0,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch->schedule(cb0,
-                                 {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+    result =
+            mDispatch->schedule(cb0, {.workDuration = 1900, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1100, *result);
 }
@@ -857,13 +827,11 @@
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
@@ -873,17 +841,17 @@
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
     SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
 
-    EXPECT_CALL(mMockClock, alarmAt(_, 600));
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(0, *result);
 
@@ -897,10 +865,10 @@
     VSyncCallbackRegistration cb(
             mDispatch, [](auto, auto, auto) {}, "");
     VSyncCallbackRegistration cb1(std::move(cb));
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -913,10 +881,10 @@
     VSyncCallbackRegistration cb1(
             mDispatch, [](auto, auto, auto) {}, "");
     cb1 = std::move(cb);
-    cb.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    cb.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 1000});
     cb.cancel();
 
-    cb1.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    cb1.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     cb1.cancel();
 }
 
@@ -929,16 +897,14 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
     mMockClock.advanceBy(80);
@@ -957,16 +923,14 @@
     CountingCallback cb(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb, {.workDuration = 370, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1630, *result);
     mMockClock.advanceBy(80);
@@ -983,12 +947,10 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
@@ -1012,12 +974,10 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2, {.workDuration = 100, .readyDuration = 0, .lastVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
@@ -1039,21 +999,21 @@
     CountingCallback cb2(mDispatch);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000, std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(1000));
 
     auto result =
-            mDispatch->schedule(cb1,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch->schedule(cb2,
-                                 {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb2, {.workDuration = 390, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(610, *result);
 
@@ -1075,10 +1035,9 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch->schedule(cb,
-                                            {.workDuration = 70,
-                                             .readyDuration = 30,
-                                             .earliestVsync = intended});
+    const auto result =
+            mDispatch->schedule(cb,
+                                {.workDuration = 70, .readyDuration = 30, .lastVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
     advanceToNextCallback();
@@ -1099,8 +1058,8 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1119,11 +1078,12 @@
 
     Sequence seq;
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
 
-    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .lastVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .lastVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1132,7 +1092,7 @@
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
     EXPECT_THAT(cb.mCalls[0], Eq(1000));
     ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
-    EXPECT_THAT(cb.mWakeupTime[0], Eq(600));
+    EXPECT_THAT(cb.mWakeupTime[0], Eq(0));
     ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
     EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
 }
@@ -1143,14 +1103,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
 
@@ -1161,22 +1119,27 @@
 TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
     SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
 
-    EXPECT_CALL(mMockClock, alarmAt(_, 500));
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq);
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch->schedule(cb,
-                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb, {.workDuration = 500, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(300);
 
-    result = mDispatch->schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb, {.workDuration = 800, .readyDuration = 0, .lastVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(300, *result);
 
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
+    EXPECT_THAT(cb.mCalls[0], Eq(1000));
+    ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+    EXPECT_THAT(cb.mWakeupTime[0], Eq(300));
+    ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+    EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
 }
 
 class VSyncDispatchTimerQueueEntryTest : public testing::Test {
@@ -1201,7 +1164,7 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1216,14 +1179,15 @@
     auto const duration = 500;
     auto const now = 8750;
 
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(now + duration, std::optional<nsecs_t>(994)))
             .Times(1)
             .WillOnce(Return(10000));
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
+    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .lastVsync = 994},
                                *mStubTracker.get(), now)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1246,7 +1210,7 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
@@ -1269,7 +1233,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_, _))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1020));
@@ -1281,7 +1245,7 @@
     entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.wakeupTime());
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto wakeup = entry.wakeupTime();
@@ -1297,7 +1261,7 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     entry.update(*mStubTracker.get(), 0);
@@ -1310,24 +1274,24 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     entry.executing(); // 1000 is executing
     // had 1000 not been executing, this could have been scheduled for time 800.
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 1001},
                                *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
@@ -1340,23 +1304,25 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     Sequence seq;
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500, std::optional<nsecs_t>(500)))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
+    EXPECT_CALL(*mStubTracker.get(),
+                nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold,
+                                             std::optional<nsecs_t>(1000)))
             .InSequence(seq)
             .WillOnce(Return(2000));
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 
     entry.executing(); // 1000 is executing
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 }
@@ -1364,16 +1330,16 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
-    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
 }
@@ -1383,9 +1349,9 @@
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
-    entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .earliestVsync = 400});
+    entry.addPendingWorkloadUpdate({.workDuration = 100, .readyDuration = 0, .lastVsync = 400});
     entry.addPendingWorkloadUpdate(
-            {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400});
+            {.workDuration = effectualOffset, .readyDuration = 0, .lastVsync = 400});
     EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
     entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
@@ -1407,7 +1373,7 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
+    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .lastVsync = 500},
                                *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 43d683d..961ba57 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -23,7 +23,10 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 #define LOG_NDEBUG 0
 
+#include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncPredictor.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -31,15 +34,31 @@
 #include <chrono>
 #include <utility>
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 using namespace testing;
 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(),
@@ -49,16 +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};
+    VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent, mVsyncTrackerCallback};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -68,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));
@@ -89,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++) {
@@ -123,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));
 }
 
@@ -169,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);
     }
@@ -188,7 +218,7 @@
     auto expectedPeriod = 11089413;
     auto expectedIntercept = 94421;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -215,7 +245,7 @@
     auto expectedPeriod = 45450152;
     auto expectedIntercept = 469647;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -241,7 +271,7 @@
     auto expectedPeriod = 1999892;
     auto expectedIntercept = 86342;
 
-    tracker.setPeriod(idealPeriod);
+    tracker.setDisplayModePtr(displayMode(idealPeriod));
     for (auto const& timestamp : simulatedVsyncs) {
         tracker.addVsyncTimestamp(timestamp);
     }
@@ -261,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);
     }
@@ -271,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);
     }
@@ -295,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);
     }
@@ -303,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));
@@ -359,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);
     }
@@ -378,8 +408,9 @@
 
 // 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,
                                                840973702473, 840990256277, 841007116851,
@@ -393,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);
     }
@@ -406,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);
     }
@@ -548,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);
     }
@@ -626,6 +657,82 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+    NotifyExpectedPresentConfig notifyExpectedPresentConfig;
+    notifyExpectedPresentConfig.timeoutNs = 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(TimePoint::fromNs(last + mPeriod), mode,
+                                     FpsMatcher(refreshRate)))
+                .Times(1);
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    tracker.setRenderRate(refreshRate / 2);
+    {
+        // out of render rate phase
+        EXPECT_CALL(mVsyncTrackerCallback,
+                    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(1000));
+
+    vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+    EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 2000));
+    EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(3500, 3500));
+
+    // Miss when starting 4500 and expect the next vsync will be at 5000 (next one)
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3500), TimePoint::fromNs(2500));
+    vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+}
+
 } // 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 122192b..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, setRenderRate, (Fps), (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 0b07745..184dada 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -51,7 +51,7 @@
     ~Composer() override;
 
     MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
-    MOCK_METHOD(bool, getDisplayConfigurationsSupported, (), (const, override));
+    MOCK_METHOD(bool, isVrrSupported, (), (const, override));
     MOCK_METHOD0(getCapabilities,
                  std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
@@ -86,18 +86,19 @@
     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));
     MOCK_METHOD2(setPowerMode, Error(Display, IComposerClient::PowerMode));
     MOCK_METHOD2(setVsyncEnabled, Error(Display, IComposerClient::Vsync));
     MOCK_METHOD1(setClientTargetSlotCount, Error(Display));
-    MOCK_METHOD4(validateDisplay, Error(Display, nsecs_t, uint32_t*, uint32_t*));
-    MOCK_METHOD6(presentOrValidateDisplay,
-                 Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
+    MOCK_METHOD(Error, validateDisplay, (Display, nsecs_t, int32_t, uint32_t*, uint32_t*));
+    MOCK_METHOD(Error, presentOrValidateDisplay,
+                (Display, nsecs_t, int32_t, uint32_t*, uint32_t*, int*, uint32_t*));
     MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
     MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
     MOCK_METHOD4(setLayerBufferSlotsToClear,
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 40f59b8..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));
@@ -76,9 +76,9 @@
                 (override));
     MOCK_METHOD(hal::Error, setPowerMode, (hal::PowerMode), (override));
     MOCK_METHOD(hal::Error, setVsyncEnabled, (hal::Vsync), (override));
-    MOCK_METHOD(hal::Error, validate, (nsecs_t, uint32_t *, uint32_t *), (override));
+    MOCK_METHOD(hal::Error, validate, (nsecs_t, int32_t, uint32_t*, uint32_t*), (override));
     MOCK_METHOD(hal::Error, presentOrValidate,
-                (nsecs_t, uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *),
+                (nsecs_t, int32_t, uint32_t*, uint32_t*, android::sp<android::Fence>*, uint32_t*),
                 (override));
     MOCK_METHOD(ftl::Future<hal::Error>, setDisplayBrightness,
                 (float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index a088aab..ed1405b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -18,12 +18,21 @@
 
 #include "binder/Status.h"
 
+// FMQ library in IPower does questionable conversions
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#pragma clang diagnostic pop
+
 #include <gmock/gmock.h>
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::IPower;
 using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionTag;
+
 using aidl::android::hardware::power::Mode;
 using android::binder::Status;
 
@@ -42,6 +51,14 @@
                  int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
                 (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, SessionTag tag, SessionConfig* config,
+                 std::shared_ptr<IPowerHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
+                (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 364618d..27564b2 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -18,10 +18,15 @@
 
 #include "binder/Status.h"
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#pragma clang diagnostic pop
+
 #include <gmock/gmock.h>
 
 using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
 using aidl::android::hardware::power::SessionHint;
 using aidl::android::hardware::power::SessionMode;
 using android::binder::Status;
@@ -47,6 +52,7 @@
     MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
     MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
     MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override));
 };
 
 } // namespace android::Hwc2::mock
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/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index 68fe3c5..b17c8ad 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -19,7 +19,10 @@
 #include <gmock/gmock.h>
 #include <scheduler/Time.h>
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <powermanager/PowerHalController.h>
+#pragma clang diagnostic pop
 
 namespace android {
 namespace hardware {
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 866af3b..e2b0ed1 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -59,6 +59,9 @@
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
+    MOCK_METHOD(void, onHdcpLevelsChanged,
+                (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel),
+                (override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 4cc78fe..4204aa0 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <gmock/gmock.h>
+#include <optional>
 
 namespace android::mock {
 
@@ -27,6 +28,13 @@
         EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
                 .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
     }
+
+    MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> uid)
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {
+        EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
+                .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
+    }
+
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
     MOCK_CONST_METHOD0(getType, const char*());
@@ -37,6 +45,7 @@
     MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
     MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace());
+    MOCK_METHOD(bool, isFrontBuffered, (), (const, override));
 };
 
 } // namespace android::mock
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 dcf25e1..3870983 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -27,15 +27,19 @@
     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(bool, addVsyncTimestamp, (nsecs_t), (override));
+    MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<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_CONST_METHOD1(dump, void(std::string&));
+    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
new file mode 100644
index 0000000..b48529f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "Scheduler/VSyncTracker.h"
+
+namespace android::scheduler::mock {
+
+struct VsyncTrackerCallback final : IVsyncTrackerCallback {
+    MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
+};
+
+struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
+    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"
         }
       ]
     }
diff --git a/vulkan/include/vulkan/vk_android_native_buffer.h b/vulkan/include/vulkan/vk_android_native_buffer.h
index 7c8e695..159b2d5 100644
--- a/vulkan/include/vulkan/vk_android_native_buffer.h
+++ b/vulkan/include/vulkan/vk_android_native_buffer.h
@@ -63,7 +63,8 @@
 /*
  * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
  *
- * This version of the extension deprecates the last of grallocusage
+ * This version of the extension deprecates the last of grallocusage and
+ * extends VkNativeBufferANDROID to support passing AHardwareBuffer*
  */
 #define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
 #define VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME "VK_ANDROID_native_buffer"
@@ -111,6 +112,9 @@
  * usage: gralloc usage requested when the buffer was allocated
  * usage2: gralloc usage requested when the buffer was allocated
  * usage3: gralloc usage requested when the buffer was allocated
+ * ahb: The AHardwareBuffer* from the actual ANativeWindowBuffer. Caller
+ *      maintains ownership of resource. AHardwareBuffer pointer is only valid
+ *      for the duration of the function call
  */
 typedef struct {
     VkStructureType                   sType;
@@ -121,6 +125,7 @@
     int                               usage; /* DEPRECATED in SPEC_VERSION 6 */
     VkNativeBufferUsage2ANDROID       usage2; /* DEPRECATED in SPEC_VERSION 9 */
     uint64_t                          usage3; /* ADDED in SPEC_VERSION 9 */
+    struct AHardwareBuffer*           ahb; /* ADDED in SPEC_VERSION 11 */
 } VkNativeBufferANDROID;
 
 /*
diff --git a/vulkan/libvulkan/Android.bp b/vulkan/libvulkan/Android.bp
index a87f82f..436e6c6 100644
--- a/vulkan/libvulkan/Android.bp
+++ b/vulkan/libvulkan/Android.bp
@@ -109,6 +109,7 @@
         "libnativeloader_lazy",
         "libnativewindow",
         "libvndksupport",
+        "libdl_android",
         "android.hardware.graphics.common@1.0",
         "libSurfaceFlingerProp",
     ],
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 5d7a4aa..0e45d2d 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -46,6 +46,8 @@
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
 
+extern "C" android_namespace_t* android_get_exported_namespace(const char*);
+
 // #define ENABLE_ALLOC_CALLSTACKS 1
 #if ENABLE_ALLOC_CALLSTACKS
 #include <utils/CallStack.h>
@@ -159,6 +161,7 @@
     "ro.board.platform",
 }};
 constexpr int LIB_DL_FLAGS = RTLD_LOCAL | RTLD_NOW;
+constexpr char RO_VULKAN_APEX_PROPERTY[] = "ro.vulkan.apex";
 
 // LoadDriver returns:
 // * 0 when succeed, or
@@ -166,6 +169,7 @@
 // * -EINVAL when fail to find HAL_MODULE_INFO_SYM_AS_STR or
 //   HWVULKAN_HARDWARE_MODULE_ID in the library.
 int LoadDriver(android_namespace_t* library_namespace,
+               const char* ns_name,
                const hwvulkan_module_t** module) {
     ATRACE_CALL();
 
@@ -183,8 +187,10 @@
                 .library_namespace = library_namespace,
             };
             so = android_dlopen_ext(lib_name.c_str(), LIB_DL_FLAGS, &dlextinfo);
-            ALOGE("Could not load %s from updatable gfx driver namespace: %s.",
-                  lib_name.c_str(), dlerror());
+            if (!so) {
+                ALOGE("Could not load %s from %s namespace: %s.",
+                      lib_name.c_str(), ns_name, dlerror());
+            }
         } else {
             // load built-in driver
             so = android_load_sphal_library(lib_name.c_str(), LIB_DL_FLAGS);
@@ -211,12 +217,30 @@
     return 0;
 }
 
+int LoadDriverFromApex(const hwvulkan_module_t** module) {
+    ATRACE_CALL();
+
+    auto apex_name = android::base::GetProperty(RO_VULKAN_APEX_PROPERTY, "");
+    if (apex_name == "") {
+        return -ENOENT;
+    }
+    // Get linker namespace for Vulkan APEX
+    std::replace(apex_name.begin(), apex_name.end(), '.', '_');
+    auto ns = android_get_exported_namespace(apex_name.c_str());
+    if (!ns) {
+        return -ENOENT;
+    }
+    android::GraphicsEnv::getInstance().setDriverToLoad(
+        android::GpuStatsInfo::Driver::VULKAN);
+    return LoadDriver(ns, apex_name.c_str(), module);
+}
+
 int LoadBuiltinDriver(const hwvulkan_module_t** module) {
     ATRACE_CALL();
 
     android::GraphicsEnv::getInstance().setDriverToLoad(
         android::GpuStatsInfo::Driver::VULKAN);
-    return LoadDriver(nullptr, module);
+    return LoadDriver(nullptr, nullptr, module);
 }
 
 int LoadUpdatedDriver(const hwvulkan_module_t** module) {
@@ -227,7 +251,7 @@
         return -ENOENT;
     android::GraphicsEnv::getInstance().setDriverToLoad(
         android::GpuStatsInfo::Driver::VULKAN_UPDATED);
-    int result = LoadDriver(ns, module);
+    int result = LoadDriver(ns, "updatable gfx driver", module);
     if (result != 0) {
         LOG_ALWAYS_FATAL(
             "couldn't find an updated Vulkan implementation from %s",
@@ -256,6 +280,9 @@
 
     result = LoadUpdatedDriver(&module);
     if (result == -ENOENT) {
+        result = LoadDriverFromApex(&module);
+    }
+    if (result == -ENOENT) {
         result = LoadBuiltinDriver(&module);
     }
     if (result != 0) {
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 0c48611..6b3c379 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1485,10 +1485,33 @@
         return VK_SUCCESS;
     }
 
+    // Look through the create_info pNext chain passed to createSwapchainKHR
+    // for an image compression control struct.
+    // if one is found AND the appropriate extensions are enabled, create a
+    // VkImageCompressionControlEXT structure to pass on to GetPhysicalDeviceImageFormatProperties2
+    void* compression_control_pNext = nullptr;
+    VkImageCompressionControlEXT image_compression = {};
+    const VkSwapchainCreateInfoKHR* create_infos = create_info;
+    while (create_infos->pNext) {
+        create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
+        switch (create_infos->sType) {
+            case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+                const VkImageCompressionControlEXT* compression_infos =
+                    reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
+                image_compression = *compression_infos;
+                image_compression.pNext = nullptr;
+                compression_control_pNext = &image_compression;
+            } break;
+            default:
+                // Ignore all other info structs
+                break;
+        }
+    }
+
     // call GetPhysicalDeviceImageFormatProperties2KHR
     VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
         .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
-        .pNext = nullptr,
+        .pNext = compression_control_pNext,
         .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
     };
 
@@ -1739,6 +1762,8 @@
     }
 
     int query_value;
+    // TODO: Now that we are calling into GPDSC2 directly, this query may be redundant
+    //       the call to std::max(min_buffer_count, num_images) may be redundant as well
     err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                         &query_value);
     if (err != android::OK || query_value < 0) {
@@ -1755,12 +1780,33 @@
     // with extra images (which they can't actually use!).
     const uint32_t min_buffer_count = min_undequeued_buffers + 1;
 
-    uint32_t num_images;
-    if (create_info->presentMode  == VK_PRESENT_MODE_MAILBOX_KHR) {
-        num_images = std::max(3u, create_info->minImageCount);
-    } else {
-        num_images = create_info->minImageCount;
-    }
+    // Call into GPDSC2 to get the minimum and maximum allowable buffer count for the surface of
+    // interest. This step is only necessary if the app requests a number of images
+    // (create_info->minImageCount) that is less or more than the surface capabilities.
+    // An app should be calling GPDSC2 and using those values to set create_info, but in the
+    // event that the app has hard-coded image counts an error can occur
+    VkSurfacePresentModeEXT present_mode = {
+        VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT,
+        nullptr,
+        create_info->presentMode
+    };
+    VkPhysicalDeviceSurfaceInfo2KHR surface_info2 = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+        &present_mode,
+        create_info->surface
+    };
+    VkSurfaceCapabilities2KHR surface_capabilities2 = {
+        VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+        nullptr,
+        {},
+    };
+    result = GetPhysicalDeviceSurfaceCapabilities2KHR(GetData(device).driver_physical_device,
+            &surface_info2, &surface_capabilities2);
+
+    uint32_t num_images = create_info->minImageCount;
+    num_images = std::clamp(num_images,
+            surface_capabilities2.surfaceCapabilities.minImageCount,
+            surface_capabilities2.surfaceCapabilities.maxImageCount);
 
     const uint32_t buffer_count = std::max(min_buffer_count, num_images);
     err = native_window_set_buffer_count(window, buffer_count);
@@ -1947,6 +1993,8 @@
                 &image_native_buffer.usage2.producer,
                 &image_native_buffer.usage2.consumer);
             image_native_buffer.usage3 = img.buffer->usage;
+            image_native_buffer.ahb =
+                ANativeWindowBuffer_getHardwareBuffer(img.buffer.get());
             image_create.pNext = &image_native_buffer;
 
             ATRACE_BEGIN("CreateImage");
@@ -2123,7 +2171,12 @@
                     .stride = buffer->stride,
                     .format = buffer->format,
                     .usage = int(buffer->usage),
+                    .usage3 = buffer->usage,
+                    .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
                 };
+                android_convertGralloc0To1Usage(int(buffer->usage),
+                                                &nb.usage2.producer,
+                                                &nb.usage2.consumer);
                 VkBindImageMemoryInfo bimi = {
                     .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                     .pNext = &nb,
@@ -2669,7 +2722,12 @@
             .stride = buffer->stride,
             .format = buffer->format,
             .usage = int(buffer->usage),
+            .usage3 = buffer->usage,
+            .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
         };
+        android_convertGralloc0To1Usage(int(buffer->usage),
+                                        &native_buffer.usage2.producer,
+                                        &native_buffer.usage2.consumer);
         // Reserve enough space to avoid letting re-allocation invalidate the
         // addresses of the elements inside.
         out_native_buffers->reserve(bind_info_count);
diff --git a/vulkan/nulldrv/Android.bp b/vulkan/nulldrv/Android.bp
index a6d540b..5112e14 100644
--- a/vulkan/nulldrv/Android.bp
+++ b/vulkan/nulldrv/Android.bp
@@ -46,5 +46,8 @@
         "hwvulkan_headers",
         "vulkan_headers",
     ],
-    shared_libs: ["liblog"],
+    shared_libs: [
+        "liblog",
+        "libnativewindow",
+    ],
 }
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 2e87f17..973e71c 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android/hardware_buffer.h>
 #include <hardware/hwvulkan.h>
 
 #include <errno.h>
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index 935535f..d34851e 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -16,6 +16,8 @@
 
 // WARNING: This file is generated. See ../README.md for instructions.
 
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"